From 367b16a3ab27ee534b4e497c2699d6fc0dc7368f Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Wed, 11 Jul 2018 16:36:03 +0100 Subject: [PATCH 001/104] removes checking params for submit modal --- .../web/app/coffee/Features/Project/ProjectController.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index e2c71a956e..5a1bbb11f6 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -346,7 +346,6 @@ module.exports = ProjectController = useV2History: !!project.overleaf?.history?.display showRichText: req.query?.rt == 'true' showTestControls: req.query?.tc == 'true' || user.isAdmin - showPublishModal: req.query?.pm == 'true' timer.done() _buildProjectList: (allProjects, v1Projects = [])-> From d990ae1a1a24080621993b9a4c3a34c72b4313ce Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Wed, 25 Jul 2018 13:59:21 -0500 Subject: [PATCH 002/104] Style for portals --- .../web/public/stylesheets/app/portals.less | 161 ++++++++++++++++++ .../components/embed-responsive.less | 26 +++ .../public/stylesheets/components/icons.less | 9 + .../stylesheets/core/_common-variables.less | 9 +- .../public/stylesheets/core/ol-variables.less | 22 +++ services/web/public/stylesheets/ol-style.less | 5 +- 6 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 services/web/public/stylesheets/app/portals.less create mode 100644 services/web/public/stylesheets/components/embed-responsive.less create mode 100644 services/web/public/stylesheets/components/icons.less diff --git a/services/web/public/stylesheets/app/portals.less b/services/web/public/stylesheets/app/portals.less new file mode 100644 index 0000000000..ae6fcdf230 --- /dev/null +++ b/services/web/public/stylesheets/app/portals.less @@ -0,0 +1,161 @@ +.content-portal { + padding-top: @navbar-height!important; + + /* + Begin Header + */ + .banner-image { + background-size: cover; + background-position: 50% 50%; + background-repeat: no-repeat; + height: 375px; + } + + .image-fill { + display: inline-block; + height: 100%; + vertical-align: middle; + } + + .institution-logo { + left: 50%; + margin-left: -100px; + padding: 0; + position: absolute; + div { + background-color: @white; + box-shadow: 1px 11px 22px -9px @black-alpha-strong; + display: inline-block; + height: 125px; + overflow: hidden; + position: absolute; + text-align: center; + top: -110px; + white-space: nowrap; + width: @btn-portal-width; + } + img { + max-height: 75px; + max-width: 150px; + vertical-align: middle; + } + } + + .portal-name { + background-color: @ol-blue-gray-0; + padding-bottom: @line-height-computed; //- center header when no tabs + padding-top: @padding-md; + text-align: center; + width: 100%; + } + // End Header + + /* + Begin Layout + */ + .button-pull, + .content-pull { + float: left; + } + + .button-pull { + text-align: right; + > a.btn { + white-space: normal; + width: @btn-portal-width; + text-align: center; + } + } + .content-pull { + padding-right: @padding-sm; + width: calc(~"100% - "@btn-portal-width); + } + // End Layout + + /* + Begin Card + */ + .card { + margin-bottom: @margin-md; + } + // End Card + + /* + Begin Actions + */ + .portal-actions { + i { + margin-bottom: @margin-sm; + } + } + // End Actions + + /* + Begin Print + */ + .print { + .hidden-print { + display: none; + } + } + // End Print + + /* + Begin Tabs + */ + .nav-tabs { + // Overrides for nav.less + background-color: @ol-blue-gray-0; + border: 0!important; + margin-bottom: @margin-md; + margin-top: -@line-height-computed; //- adjusted for portal-name + padding: @padding-lg 0 @padding-md; + text-align: center; + + a { + color: @link-color; + &:hover { + background-color: transparent!important; + border: 0!important; + color: @link-hover-color!important; + } + } + + li { + display: inline-block; + float: none; + a { + border: 0; + } + } + + li.active > a { + background-color: transparent!important; + border: 0; + border-bottom: 1px solid @accent-color-secondary!important; + color: @accent-color-secondary; + &:hover { + color: @accent-color-secondary!important; + } + } + } + + .tab-content:extend(.container) { + background-color: transparent!important; + border: none!important; + } + // End Tabs + + @media (max-width: @screen-size-sm-max) { + .content-pull { + padding: 0; + width: auto; + } + + .button-pull { + > a.btn { + width: auto; + } + } + } +} diff --git a/services/web/public/stylesheets/components/embed-responsive.less b/services/web/public/stylesheets/components/embed-responsive.less new file mode 100644 index 0000000000..997f100449 --- /dev/null +++ b/services/web/public/stylesheets/components/embed-responsive.less @@ -0,0 +1,26 @@ +.embed-responsive { + display: block; + height: 0; + overflow: hidden; + padding: 0; + position: relative; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25% !important; +} +.embed-responsive-4by3 { + padding-bottom: 75% !important; +} \ No newline at end of file diff --git a/services/web/public/stylesheets/components/icons.less b/services/web/public/stylesheets/components/icons.less new file mode 100644 index 0000000000..fa808ad5ae --- /dev/null +++ b/services/web/public/stylesheets/components/icons.less @@ -0,0 +1,9 @@ +// Colors +.icon-accent { + color: @accent-color-secondary; +} + +// Sizes +.icon-lg { + font-size: @font-size-h1; +} \ No newline at end of file diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less index fefe12da73..db6fbf3a7e 100644 --- a/services/web/public/stylesheets/core/_common-variables.less +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -62,9 +62,12 @@ // //## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start). -@margin-sm: 10px; -@margin-md: 20px; -@margin-lg: 30px; +@margin-xs: 5px; +@margin-sm: 10px; +@margin-md: 20px; +@margin-lg: 30px; +@margin-xl: 40px; +@margin-xxl: 50px; @padding-base-vertical: 5px; @padding-base-horizontal: 16px; diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index 0e05ccdd68..d3c21dff6a 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -6,6 +6,7 @@ @footer-height: 50px; // Styleguide colors +@ol-blue-gray-0 : #f4f5f8; @ol-blue-gray-1 : #E4E8EE; @ol-blue-gray-2 : #9DA7B7; @ol-blue-gray-3 : #5D6879; @@ -21,6 +22,8 @@ @ol-dark-red : #A6312B; @ol-type-color : @ol-blue-gray-3; +@accent-color-primary: @ol-green; +@accent-color-secondary: @ol-dark-green; // Navbar customization @navbar-title-color : @ol-blue-gray-1; @@ -65,8 +68,14 @@ @btn-info-bg : @ol-blue; @btn-info-border : transparent; +// Padding @padding-xs-horizontal : 8px; +@padding-sm: 10px; +@padding-md: 20px; +@padding-lg: 30px; +@padding-xl: 40px; + // Alerts @alert-padding : 15px; @alert-border-radius : @border-radius-base; @@ -167,6 +176,9 @@ @folders-tag-menu-hover : rgba(0, 0, 0, .1); @folders-tag-menu-active-hover : rgba(0, 0, 0, .1); +// Portal +@btn-portal-width : 200px; + // Project table @structured-list-line-height : 2.5; @structured-list-link-color : @ol-blue; @@ -273,6 +285,9 @@ @log-line-no-color : #FFF; @log-hints-color : @ol-blue-gray-4; +// Portals +@black-alpha-strong : rgba(0,0,0,0.8); + // v2 History @history-base-font-size : @font-size-small; @@ -284,6 +299,12 @@ @history-toolbar-bg-color : @editor-toolbar-bg; @history-toolbar-color : #FFF; +// Screens +// added -size to not conflict with common_variables +@screen-size-sm-max : 767px; +@screen-size-md-min : 768px; +@screen-size-md-max : 991px; + // System messages @sys-msg-background : @ol-blue; @sys-msg-color : #FFF; @@ -300,6 +321,7 @@ @gray-light: #a4a4a4; @gray-lighter: #cfcfcf; @gray-lightest: #f0f0f0; +@white: #ffffff; @blue: #405ebf; @blueDark: #040D2D; diff --git a/services/web/public/stylesheets/ol-style.less b/services/web/public/stylesheets/ol-style.less index 03e2bb3ed6..774e70a2ab 100644 --- a/services/web/public/stylesheets/ol-style.less +++ b/services/web/public/stylesheets/ol-style.less @@ -6,8 +6,11 @@ @import "app/ol-style-guide.less"; @import "_style_includes.less"; @import "_ol_style_includes.less"; +@import "components/embed-responsive.less"; +@import "components/icons.less"; // Pages @import "app/about.less"; @import "app/blog-posts.less"; -@import "app/cms-page.less"; \ No newline at end of file +@import "app/cms-page.less"; +@import "app/portals.less"; \ No newline at end of file From c7941ac00e474cab6dede5003ef1f09e80ce3f61 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Wed, 25 Jul 2018 14:39:40 -0500 Subject: [PATCH 003/104] Links mixins --- services/web/app/views/_mixins_links.pug | 75 ++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 services/web/app/views/_mixins_links.pug diff --git a/services/web/app/views/_mixins_links.pug b/services/web/app/views/_mixins_links.pug new file mode 100644 index 0000000000..18b208270c --- /dev/null +++ b/services/web/app/views/_mixins_links.pug @@ -0,0 +1,75 @@ +mixin linkAdvisors(linkText, linkClass, tracked) + //- To Do: verify path + //- To Do: track + a(href="/advisors" + class=linkClass ? linkClass : '' + eventTrackingGa=track ? 'advisors' : null + ) + | #{linkText ? linkText : 'advisor programme'} + +mixin linkBenefits(linkText, linkClass) + //- To Do: verify path + a(href="/benefits" class=linkClass ? linkClass : '') + | #{linkText ? linkText : 'benefits'} + +mixin linkBlog(linkText, linkClass, slug) + if slug + a(href="/blog/#{slug}" class=linkClass ? linkClass : '') + | #{linkText ? linkText : 'blog'} + +mixin linkContact(linkText, linkClass) + a(href="/contact" class=linkClass ? linkClass : '') + | #{linkText ? linkText : 'contact'} + +mixin linkEducation(linkText, linkClass) + //- To Do: verify path + a(href="/plans" class=linkClass ? linkClass : '') + | #{linkText ? linkText : 'teaching toolkit'} + +mixin linkEmail(linkText, linkClass, email) + //- To Do: env var? + - var emailDomain = 'overleaf.com' + a(href="mailto:#{email ? email : 'contact'}@#{emailDomain}" class=linkClass ? linkClass : '') + | #{linkText ? linkText : 'email'} + +mixin linkInvite(linkText, linkClass) + a(href="/user/bonus" class=linkClass ? linkClass : '') + | #{linkText ? linkText : 'invite your friends'} + +mixin linkPlansAndPricing(linkText, linkClass) + //- To Do: verify path + a(href="/plans" class=linkClass ? linkClass : '') + | #{linkText ? linkText : 'plans and pricing'} + +mixin linkPrintNewTab(linkText, linkClass, icon, track) + //- To Do: track + a(href='?media=print' + class=linkClass ? linkClass : '' + eventTrackingGa=track ? 'print' : null + target="_BLANK" + ) + if icon + i(class="fa fa-print") + |   + | #{linkText ? linkText : 'print'} + +mixin linkSignIn(linkText, linkClass) + a(href="/login" class=linkClass ? linkClass : '') + | #{linkText ? linkText : 'sign in'} + +mixin linkSignUp(linkText, linkClass) + a(href="/register" class=linkClass ? linkClass : '') + | #{linkText ? linkText : 'sign up'} + +mixin linkTweet(linkText, linkClass, tweetText, track) + //- twitter-share-button is required by twitter + //- To Do: track + a(class="twitter-share-button " + linkClass + href="https://twitter.com/intent/tweet?text=" + tweetText + target="_BLANK" + ) #{linkText ? linkText : 'tweet'} + +mixin linkUniversities(linkText, linkClass) + //- To Do: verify path + a(href="/universities" class=linkClass ? linkClass : '') + | #{linkText ? linkText : 'universities'} From 96a49a7fe6c574c89fcb8dfe81bef82e00faf1a5 Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Thu, 26 Jul 2018 12:53:28 +0100 Subject: [PATCH 004/104] cleaning up date range picker icons and buttons --- .../components/daterange-picker.less | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/services/web/public/stylesheets/components/daterange-picker.less b/services/web/public/stylesheets/components/daterange-picker.less index 2d15d2a36d..f0dcc01d27 100644 --- a/services/web/public/stylesheets/components/daterange-picker.less +++ b/services/web/public/stylesheets/components/daterange-picker.less @@ -34,7 +34,7 @@ @daterangepicker-in-range-bg-color: #ebf4f8; @daterangepicker-active-color: #fff; -@daterangepicker-active-bg-color: #a93529; +@daterangepicker-active-bg-color: #4F9C45; @daterangepicker-active-border-color: transparent; @daterangepicker-unselected-color: #999; @@ -393,13 +393,14 @@ .daterangepicker_input { position: relative; + padding-left: 0; i { position: absolute; // NOTE: These appear to be eyeballed to me... left: 8px; - top: 8px; + top: 10px; } } &.rtl { @@ -476,6 +477,19 @@ /* Larger Screen Styling */ @media (min-width: 564px) { .daterangepicker { + .glyphicon { + font-family: FontAwesome; + } + .glyphicon-chevron-left:before{ + content: "\f053"; + } + .glyphicon-chevron-right:before{ + content: "\f054"; + } + .glyphicon-calendar:before{ + content: "\f073"; + } + width: auto; .ranges { From 4a22129aa0d94afbf201177186f648e57c9ef628 Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Thu, 26 Jul 2018 16:50:48 +0100 Subject: [PATCH 005/104] add missing v1 metrics styling for graphs and updates --- .../public/stylesheets/_style_includes.less | 2 + .../web/public/stylesheets/app/metrics.less | 5 + .../public/stylesheets/components/nvd3.less | 674 ++++++++++++++++++ .../stylesheets/components/nvd3_override.less | 16 + 4 files changed, 697 insertions(+) create mode 100755 services/web/public/stylesheets/components/nvd3.less create mode 100644 services/web/public/stylesheets/components/nvd3_override.less diff --git a/services/web/public/stylesheets/_style_includes.less b/services/web/public/stylesheets/_style_includes.less index 968304ba3d..88a4ad59bc 100644 --- a/services/web/public/stylesheets/_style_includes.less +++ b/services/web/public/stylesheets/_style_includes.less @@ -43,6 +43,8 @@ @import "components/hover.less"; @import "components/ui-select.less"; @import "components/input-suggestions.less"; +@import "components/nvd3.less"; +@import "components/nvd3_override.less"; // Components w/ JavaScript @import "components/modals.less"; diff --git a/services/web/public/stylesheets/app/metrics.less b/services/web/public/stylesheets/app/metrics.less index 23d121088d..244021bcad 100644 --- a/services/web/public/stylesheets/app/metrics.less +++ b/services/web/public/stylesheets/app/metrics.less @@ -87,6 +87,11 @@ margin-top: 0; margin-bottom: 0; } + + .metric-tooltip { + top: -1em; + font-size: .5em; + } } // END: Metrics header diff --git a/services/web/public/stylesheets/components/nvd3.less b/services/web/public/stylesheets/components/nvd3.less new file mode 100755 index 0000000000..5ddcdc7a01 --- /dev/null +++ b/services/web/public/stylesheets/components/nvd3.less @@ -0,0 +1,674 @@ +/* nvd3 version 1.8.4 (https://github.com/novus/nvd3) 2016-07-03 */ +.nvd3 .nv-axis { + pointer-events:none; + opacity: 1; +} + +.nvd3 .nv-axis path { + fill: none; + stroke: #000; + stroke-opacity: .75; + shape-rendering: crispEdges; +} + +.nvd3 .nv-axis path.domain { + stroke-opacity: .75; +} + +.nvd3 .nv-axis.nv-x path.domain { + stroke-opacity: 0; +} + +.nvd3 .nv-axis line { + fill: none; + stroke: #e5e5e5; + shape-rendering: crispEdges; +} + +.nvd3 .nv-axis .zero line, + /*this selector may not be necessary*/ .nvd3 .nv-axis line.zero { + stroke-opacity: .75; +} + +.nvd3 .nv-axis .nv-axisMaxMin text { + font-weight: bold; +} + +.nvd3 .x .nv-axis .nv-axisMaxMin text, +.nvd3 .x2 .nv-axis .nv-axisMaxMin text, +.nvd3 .x3 .nv-axis .nv-axisMaxMin text { + text-anchor: middle +} + +.nvd3 .nv-axis.nv-disabled { + opacity: 0; +} + +.nvd3 .nv-bars rect { + fill-opacity: .75; + + transition: fill-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear; +} + +.nvd3 .nv-bars rect.hover { + fill-opacity: 1; +} + +.nvd3 .nv-bars .hover rect { + fill: lightblue; +} + +.nvd3 .nv-bars text { + fill: rgba(0,0,0,0); +} + +.nvd3 .nv-bars .hover text { + fill: rgba(0,0,0,1); +} + +.nvd3 .nv-multibar .nv-groups rect, +.nvd3 .nv-multibarHorizontal .nv-groups rect, +.nvd3 .nv-discretebar .nv-groups rect { + stroke-opacity: 0; + + transition: fill-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear; +} + +.nvd3 .nv-multibar .nv-groups rect:hover, +.nvd3 .nv-multibarHorizontal .nv-groups rect:hover, +.nvd3 .nv-candlestickBar .nv-ticks rect:hover, +.nvd3 .nv-discretebar .nv-groups rect:hover { + fill-opacity: 1; +} + +.nvd3 .nv-discretebar .nv-groups text, +.nvd3 .nv-multibarHorizontal .nv-groups text { + font-weight: bold; + fill: rgba(0,0,0,1); + stroke: rgba(0,0,0,0); +} + +/* boxplot CSS */ +.nvd3 .nv-boxplot circle { + fill-opacity: 0.5; +} + +.nvd3 .nv-boxplot circle:hover { + fill-opacity: 1; +} + +.nvd3 .nv-boxplot rect:hover { + fill-opacity: 1; +} + +.nvd3 line.nv-boxplot-median { + stroke: black; +} + +.nv-boxplot-tick:hover { + stroke-width: 2.5px; +} +/* bullet */ +.nvd3.nv-bullet { font: 10px sans-serif; } +.nvd3.nv-bullet .nv-measure { fill-opacity: .8; } +.nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; } +.nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; } +.nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; } +.nvd3.nv-bullet .nv-markerLine { stroke: #000; stroke-width: 1.5px; } +.nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; } +.nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; } +.nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; } +.nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; } +.nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; } +.nvd3.nv-bullet .nv-subtitle { fill: #999; } + + +.nvd3.nv-bullet .nv-range { + fill: #bababa; + fill-opacity: .4; +} +.nvd3.nv-bullet .nv-range:hover { + fill-opacity: .7; +} + +.nvd3.nv-candlestickBar .nv-ticks .nv-tick { + stroke-width: 1px; +} + +.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover { + stroke-width: 2px; +} + +.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect { + stroke: #2ca02c; + fill: #2ca02c; +} + +.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect { + stroke: #d62728; + fill: #d62728; +} + +.with-transitions .nv-candlestickBar .nv-ticks .nv-tick { + transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + +} + +.nvd3.nv-candlestickBar .nv-ticks line { + stroke: #333; +} + + +.nv-force-node { + stroke: #fff; + stroke-width: 1.5px; +} +.nv-force-link { + stroke: #999; + stroke-opacity: .6; +} +.nv-force-node text { + stroke-width: 0px +} + +.nvd3 .nv-legend .nv-disabled rect { + /*fill-opacity: 0;*/ +} + +.nvd3 .nv-check-box .nv-box { + fill-opacity:0; + stroke-width:2; +} + +.nvd3 .nv-check-box .nv-check { + fill-opacity:0; + stroke-width:4; +} + +.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check { + fill-opacity:0; + stroke-opacity:0; +} + +.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check { + opacity: 0; +} + +/* line plus bar */ +.nvd3.nv-linePlusBar .nv-bar rect { + fill-opacity: .75; +} + +.nvd3.nv-linePlusBar .nv-bar rect:hover { + fill-opacity: 1; +} +.nvd3 .nv-groups path.nv-line { + fill: none; +} + +.nvd3 .nv-groups path.nv-area { + stroke: none; +} + +.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point { + fill-opacity: 0; + stroke-opacity: 0; +} + +.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point { + fill-opacity: .5 !important; + stroke-opacity: .5 !important; +} + + +.with-transitions .nvd3 .nv-groups .nv-point { + transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + +} + +.nvd3.nv-scatter .nv-groups .nv-point.hover, +.nvd3 .nv-groups .nv-point.hover { + stroke-width: 7px; + fill-opacity: .95 !important; + stroke-opacity: .95 !important; +} + + +.nvd3 .nv-point-paths path { + stroke: #aaa; + stroke-opacity: 0; + fill: #eee; + fill-opacity: 0; +} + + + +.nvd3 .nv-indexLine { + cursor: ew-resize; +} + +/******************** + * SVG CSS + */ + +/******************** + Default CSS for an svg element nvd3 used +*/ +svg.nvd3-svg { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -ms-user-select: none; + -moz-user-select: none; + user-select: none; + display: block; + width:100%; + height:100%; +} + +/******************** + Box shadow and border radius styling +*/ +.nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip { + -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); + -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); + box-shadow: 0 5px 10px rgba(0,0,0,.2); + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + + +.nvd3 text { + font: normal 12px Arial; +} + +.nvd3 .title { + font: bold 14px Arial; +} + +.nvd3 .nv-background { + fill: white; + fill-opacity: 0; +} + +.nvd3.nv-noData { + font-size: 18px; + font-weight: bold; +} + + +/********** +* Brush +*/ + +.nv-brush .extent { + fill-opacity: .125; + shape-rendering: crispEdges; +} + +.nv-brush .resize path { + fill: #eee; + stroke: #666; +} + + +/********** +* Legend +*/ + +.nvd3 .nv-legend .nv-series { + cursor: pointer; +} + +.nvd3 .nv-legend .nv-disabled circle { + fill-opacity: 0; +} + +/* focus */ +.nvd3 .nv-brush .extent { + fill-opacity: 0 !important; +} + +.nvd3 .nv-brushBackground rect { + stroke: #000; + stroke-width: .4; + fill: #fff; + fill-opacity: .7; +} + +/********** +* Print +*/ + +@media print { + .nvd3 text { + stroke-width: 0; + fill-opacity: 1; + } +} + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick { + stroke-width: 1px; +} + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover { + stroke-width: 2px; +} + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive { + stroke: #2ca02c; +} + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative { + stroke: #d62728; +} + + +.nvd3 .background path { + fill: none; + stroke: #EEE; + stroke-opacity: .4; + shape-rendering: crispEdges; +} + +.nvd3 .foreground path { + fill: none; + stroke-opacity: .7; +} + +.nvd3 .nv-parallelCoordinates-brush .extent +{ + fill: #fff; + fill-opacity: .6; + stroke: gray; + shape-rendering: crispEdges; +} + +.nvd3 .nv-parallelCoordinates .hover { + fill-opacity: 1; + stroke-width: 3px; +} + + +.nvd3 .missingValuesline line { + fill: none; + stroke: black; + stroke-width: 1; + stroke-opacity: 1; + stroke-dasharray: 5, 5; +} +.nvd3.nv-pie path { + stroke-opacity: 0; + transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; + +} + +.nvd3.nv-pie .nv-pie-title { + font-size: 24px; + fill: rgba(19, 196, 249, 0.59); +} + +.nvd3.nv-pie .nv-slice text { + stroke: #000; + stroke-width: 0; +} + +.nvd3.nv-pie path { + stroke: #fff; + stroke-width: 1px; + stroke-opacity: 1; +} + +.nvd3.nv-pie path { + fill-opacity: .7; +} +.nvd3.nv-pie .hover path { + fill-opacity: 1; +} +.nvd3.nv-pie .nv-label { + pointer-events: none; +} +.nvd3.nv-pie .nv-label rect { + fill-opacity: 0; + stroke-opacity: 0; +} + +/* scatter */ +.nvd3 .nv-groups .nv-point.hover { + stroke-width: 20px; + stroke-opacity: .5; +} + +.nvd3 .nv-scatter .nv-point.hover { + fill-opacity: 1; +} +.nv-noninteractive { + pointer-events: none; +} + +.nv-distx, .nv-disty { + pointer-events: none; +} + +/* sparkline */ +.nvd3.nv-sparkline path { + fill: none; +} + +.nvd3.nv-sparklineplus g.nv-hoverValue { + pointer-events: none; +} + +.nvd3.nv-sparklineplus .nv-hoverValue line { + stroke: #333; + stroke-width: 1.5px; +} + +.nvd3.nv-sparklineplus, +.nvd3.nv-sparklineplus g { + pointer-events: all; +} + +.nvd3 .nv-hoverArea { + fill-opacity: 0; + stroke-opacity: 0; +} + +.nvd3.nv-sparklineplus .nv-xValue, +.nvd3.nv-sparklineplus .nv-yValue { + stroke-width: 0; + font-size: .9em; + font-weight: normal; +} + +.nvd3.nv-sparklineplus .nv-yValue { + stroke: #f66; +} + +.nvd3.nv-sparklineplus .nv-maxValue { + stroke: #2ca02c; + fill: #2ca02c; +} + +.nvd3.nv-sparklineplus .nv-minValue { + stroke: #d62728; + fill: #d62728; +} + +.nvd3.nv-sparklineplus .nv-currentValue { + font-weight: bold; + font-size: 1.1em; +} +/* stacked area */ +.nvd3.nv-stackedarea path.nv-area { + fill-opacity: .7; + stroke-opacity: 0; + transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; +} + +.nvd3.nv-stackedarea path.nv-area.hover { + fill-opacity: .9; +} + + +.nvd3.nv-stackedarea .nv-groups .nv-point { + stroke-opacity: 0; + fill-opacity: 0; +} + + +.nvtooltip { + position: absolute; + background-color: rgba(255,255,255,1.0); + color: rgba(0,0,0,1.0); + padding: 1px; + border: 1px solid rgba(0,0,0,.2); + z-index: 10000; + display: block; + + font-family: Arial; + font-size: 13px; + text-align: left; + pointer-events: none; + + white-space: nowrap; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.nvtooltip { + background: rgba(255,255,255, 0.8); + border: 1px solid rgba(0,0,0,0.5); + border-radius: 4px; +} + +/*Give tooltips that old fade in transition by + putting a "with-transitions" class on the container div. +*/ +.nvtooltip.with-transitions, .with-transitions .nvtooltip { + transition: opacity 50ms linear; + -moz-transition: opacity 50ms linear; + -webkit-transition: opacity 50ms linear; + + transition-delay: 200ms; + -moz-transition-delay: 200ms; + -webkit-transition-delay: 200ms; +} + +.nvtooltip.x-nvtooltip, +.nvtooltip.y-nvtooltip { + padding: 8px; +} + +.nvtooltip h3 { + margin: 0; + padding: 4px 14px; + line-height: 18px; + font-weight: normal; + background-color: rgba(247,247,247,0.75); + color: rgba(0,0,0,1.0); + text-align: center; + + border-bottom: 1px solid #ebebeb; + + -webkit-border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + border-radius: 5px 5px 0 0; +} + +.nvtooltip p { + margin: 0; + padding: 5px 14px; + text-align: center; +} + +.nvtooltip span { + display: inline-block; + margin: 2px 0; +} + +.nvtooltip table { + margin: 6px; + border-spacing:0; +} + + +.nvtooltip table td { + padding: 2px 9px 2px 0; + vertical-align: middle; +} + +.nvtooltip table td.key { + font-weight: normal; +} +.nvtooltip table td.key.total { + font-weight: bold; +} +.nvtooltip table td.value { + text-align: right; + font-weight: bold; +} + +.nvtooltip table td.percent { + color: darkgray; +} + +.nvtooltip table tr.highlight td { + padding: 1px 9px 1px 0; + border-bottom-style: solid; + border-bottom-width: 1px; + border-top-style: solid; + border-top-width: 1px; +} + +.nvtooltip table td.legend-color-guide div { + width: 8px; + height: 8px; + vertical-align: middle; +} + +.nvtooltip table td.legend-color-guide div { + width: 12px; + height: 12px; + border: 1px solid #999; +} + +.nvtooltip .footer { + padding: 3px; + text-align: center; +} + +.nvtooltip-pending-removal { + pointer-events: none; + display: none; +} + + +/**** +Interactive Layer +*/ +.nvd3 .nv-interactiveGuideLine { + pointer-events:none; +} +.nvd3 line.nv-guideline { + stroke: #ccc; +} diff --git a/services/web/public/stylesheets/components/nvd3_override.less b/services/web/public/stylesheets/components/nvd3_override.less new file mode 100644 index 0000000000..2a84f27ddc --- /dev/null +++ b/services/web/public/stylesheets/components/nvd3_override.less @@ -0,0 +1,16 @@ +.nvd3 { + .nv-axis { + .tick { + line { + opacity: 0; + } + } + path.domain { + opacity: 0; + } + } +} + +svg.nvd3-iddle { + @extend svg.nvd3-svg; +} From ddbe1a5b22366d917237692acd5f36a1ae020e24 Mon Sep 17 00:00:00 2001 From: Michael Mazour Date: Fri, 27 Jul 2018 15:42:37 +0100 Subject: [PATCH 006/104] Add compiler and imageName to export API request --- .../web/app/coffee/Features/Exports/ExportsHandler.coffee | 3 +++ .../test/unit/coffee/Exports/ExportsHandlerTests.coffee | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/services/web/app/coffee/Features/Exports/ExportsHandler.coffee b/services/web/app/coffee/Features/Exports/ExportsHandler.coffee index 17a0798ead..0594b9fc2b 100644 --- a/services/web/app/coffee/Features/Exports/ExportsHandler.coffee +++ b/services/web/app/coffee/Features/Exports/ExportsHandler.coffee @@ -57,6 +57,9 @@ module.exports = ExportsHandler = self = historyId: project.overleaf?.history?.id historyVersion: historyVersion v1ProjectId: project.overleaf?.id + metadata: + compiler: project.compiler + imageName: project.imageName user: id: user_id firstName: user.first_name diff --git a/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee b/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee index a210532e9b..974e8e0c1b 100644 --- a/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee +++ b/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee @@ -63,6 +63,8 @@ describe 'ExportsHandler', -> beforeEach (done) -> @project = id: @project_id + compiler: 'pdflatex' + imageName: 'mock-image-name' overleaf: id: @project_history_id # for projects imported from v1 history: @@ -100,6 +102,9 @@ describe 'ExportsHandler', -> historyId: @project_history_id historyVersion: @historyVersion v1ProjectId: @project_history_id + metadata: + compiler: 'pdflatex' + imageName: 'mock-image-name' user: id: @user_id firstName: @user.first_name @@ -132,6 +137,9 @@ describe 'ExportsHandler', -> historyId: @project_history_id historyVersion: @historyVersion v1ProjectId: @project_history_id + metadata: + compiler: 'pdflatex' + imageName: 'mock-image-name' user: id: @user_id firstName: @custom_first_name From b04c7d2ffe08922794049c2b1d5363f2f33d2f38 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Mon, 30 Jul 2018 18:16:52 +0100 Subject: [PATCH 007/104] send clsi response codes to graphite --- services/web/app/coffee/Features/Compile/ClsiManager.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/app/coffee/Features/Compile/ClsiManager.coffee b/services/web/app/coffee/Features/Compile/ClsiManager.coffee index 9a4e61a063..59822c7547 100755 --- a/services/web/app/coffee/Features/Compile/ClsiManager.coffee +++ b/services/web/app/coffee/Features/Compile/ClsiManager.coffee @@ -100,6 +100,7 @@ module.exports = ClsiManager = timer = new Metrics.Timer("compile.currentBackend") request opts, (err, response, body)-> timer.done() + metrics.inc "compile.currentBackend.response.#{response.statusCode}" if err? logger.err err:err, project_id:project_id, url:opts?.url, "error making request to clsi" return callback(err) @@ -111,6 +112,7 @@ module.exports = ClsiManager = newBackend: (cb)-> startTime = new Date() ClsiManager._makeNewBackendRequest project_id, opts, (err, response, body)-> + metrics.inc "compile.newBackend.response.#{response.statusCode}" cb(err, {response:response, body:body, finishTime:new Date() - startTime}) }, (err, results)-> timeDifference = results.newBackend?.finishTime - results.currentBackend?.finishTime From 3532a29d50e733d951b5691202c341a2a264294e Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Mon, 30 Jul 2018 19:14:59 +0100 Subject: [PATCH 008/104] allow fetching of export zips --- .../Features/Exports/ExportsController.coffee | 7 +++++++ .../Features/Exports/ExportsHandler.coffee | 16 ++++++++++++++++ services/web/app/coffee/router.coffee | 1 + 3 files changed, 24 insertions(+) diff --git a/services/web/app/coffee/Features/Exports/ExportsController.coffee b/services/web/app/coffee/Features/Exports/ExportsController.coffee index 7c74d14aed..fab953c298 100644 --- a/services/web/app/coffee/Features/Exports/ExportsController.coffee +++ b/services/web/app/coffee/Features/Exports/ExportsController.coffee @@ -37,3 +37,10 @@ module.exports = status_detail: parsed_export.status_detail } res.send export_json: json + + exportZip: (req, res) -> + {export_id} = req.params + ExportsHandler.fetchZip export_id, (err, export_zip_url) -> + return err if err? + + res.redirect export_zip_url diff --git a/services/web/app/coffee/Features/Exports/ExportsHandler.coffee b/services/web/app/coffee/Features/Exports/ExportsHandler.coffee index 17a0798ead..32873bb6a0 100644 --- a/services/web/app/coffee/Features/Exports/ExportsHandler.coffee +++ b/services/web/app/coffee/Features/Exports/ExportsHandler.coffee @@ -115,3 +115,19 @@ module.exports = ExportsHandler = self = err = new Error("v1 export returned a failure status code: #{res.statusCode}") logger.err err:err, export:export_id, "v1 export returned failure status code: #{res.statusCode}" callback err + + fetchZip: (export_id, callback=(err, zip_url) ->) -> + console.log("#{settings.apis.v1.url}/api/v1/sharelatex/exports/#{export_id}/zip_url") + request.get { + url: "#{settings.apis.v1.url}/api/v1/sharelatex/exports/#{export_id}/zip_url" + auth: {user: settings.apis.v1.user, pass: settings.apis.v1.pass } + }, (err, res, body) -> + if err? + logger.err err:err, export:export_id, "error making request to v1 export" + callback err + else if 200 <= res.statusCode < 300 + callback null, body + else + err = new Error("v1 export returned a failure status code: #{res.statusCode}") + logger.err err:err, export:export_id, "v1 export zip fetch returned failure status code: #{res.statusCode}" + callback err diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index f35cd6c0ab..f48882a634 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -245,6 +245,7 @@ module.exports = class Router webRouter.post '/project/:project_id/export/:brand_variation_id', AuthorizationMiddlewear.ensureUserCanAdminProject, ExportsController.exportProject webRouter.get '/project/:project_id/export/:export_id', AuthorizationMiddlewear.ensureUserCanAdminProject, ExportsController.exportStatus + webRouter.get '/project/:project_id/export/:export_id/zip', AuthorizationMiddlewear.ensureUserCanAdminProject, ExportsController.exportZip webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects From 395406407e917123b0f32fb29077e1173b54aa97 Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Mon, 30 Jul 2018 19:39:35 +0100 Subject: [PATCH 009/104] add test for export fetch zip --- .../coffee/Exports/ExportsHandlerTests.coffee | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee b/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee index a210532e9b..50c95282f5 100644 --- a/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee +++ b/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee @@ -273,3 +273,32 @@ describe 'ExportsHandler', -> @callback.calledWith(null, { body: @body }) .should.equal true + describe 'fetchZip', -> + beforeEach (done) -> + @settings.apis = + v1: + url: 'http://localhost:5000' + user: 'overleaf' + pass: 'pass' + @export_id = 897 + @body = "https://writelatex-conversions-dev.s3.amazonaws.com/exports/ieee_latexqc/tnb/2912/xggmprcrpfwbsnqzqqmvktddnrbqkqkr.zip?X-Amz-Expires=14400&X-Amz-Date=20180730T181003Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJDGDIJFGLNVGZH6A/20180730/us-east-1/s3/aws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=dec990336913cef9933f0e269afe99722d7ab2830ebf2c618a75673ee7159fee" + @stubGet = sinon.stub().yields(null, {statusCode: 200}, { body: @body }) + done() + + describe "when all goes well", -> + beforeEach (done) -> + @stubRequest.get = @stubGet + @ExportsHandler.fetchZip @export_id, (error, body) => + @callback(error, body) + done() + + it 'should issue the request', -> + expect(@stubGet.getCall(0).args[0]).to.deep.equal + url: @settings.apis.v1.url + '/api/v1/sharelatex/exports/' + @export_id + '/zip_url' + auth: + user: @settings.apis.v1.user + pass: @settings.apis.v1.pass + + it 'should return the v1 export id', -> + @callback.calledWith(null, { body: @body }) + .should.equal true From 8792e600d2d0dab130b33345fa2cc886368f40cd Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Tue, 31 Jul 2018 12:43:39 +0100 Subject: [PATCH 010/104] authenticate publish menu zip downloads --- .../web/app/coffee/Features/Exports/ExportsController.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/app/coffee/Features/Exports/ExportsController.coffee b/services/web/app/coffee/Features/Exports/ExportsController.coffee index fab953c298..e724951d2d 100644 --- a/services/web/app/coffee/Features/Exports/ExportsController.coffee +++ b/services/web/app/coffee/Features/Exports/ExportsController.coffee @@ -40,6 +40,7 @@ module.exports = exportZip: (req, res) -> {export_id} = req.params + AuthenticationController.getLoggedInUserId(req) ExportsHandler.fetchZip export_id, (err, export_zip_url) -> return err if err? From 5bb3acc1ebaccfce5e9746a4b04d67dbd8defd5a Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 31 Jul 2018 12:56:35 +0100 Subject: [PATCH 011/104] fix metrics sending to clsi --- services/web/app/coffee/Features/Compile/ClsiManager.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/app/coffee/Features/Compile/ClsiManager.coffee b/services/web/app/coffee/Features/Compile/ClsiManager.coffee index 59822c7547..bfa508a3d9 100755 --- a/services/web/app/coffee/Features/Compile/ClsiManager.coffee +++ b/services/web/app/coffee/Features/Compile/ClsiManager.coffee @@ -100,7 +100,7 @@ module.exports = ClsiManager = timer = new Metrics.Timer("compile.currentBackend") request opts, (err, response, body)-> timer.done() - metrics.inc "compile.currentBackend.response.#{response.statusCode}" + Metrics.inc "compile.currentBackend.response.#{response.statusCode}" if err? logger.err err:err, project_id:project_id, url:opts?.url, "error making request to clsi" return callback(err) @@ -112,7 +112,7 @@ module.exports = ClsiManager = newBackend: (cb)-> startTime = new Date() ClsiManager._makeNewBackendRequest project_id, opts, (err, response, body)-> - metrics.inc "compile.newBackend.response.#{response.statusCode}" + Metrics.inc "compile.newBackend.response.#{response.statusCode}" cb(err, {response:response, body:body, finishTime:new Date() - startTime}) }, (err, results)-> timeDifference = results.newBackend?.finishTime - results.currentBackend?.finishTime From 89efa7e83ed681564f32f43928dc22ca7ab7cc62 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 31 Jul 2018 13:07:57 +0100 Subject: [PATCH 012/104] add null check on response --- services/web/app/coffee/Features/Compile/ClsiManager.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/app/coffee/Features/Compile/ClsiManager.coffee b/services/web/app/coffee/Features/Compile/ClsiManager.coffee index bfa508a3d9..8d5c5b2baa 100755 --- a/services/web/app/coffee/Features/Compile/ClsiManager.coffee +++ b/services/web/app/coffee/Features/Compile/ClsiManager.coffee @@ -100,7 +100,7 @@ module.exports = ClsiManager = timer = new Metrics.Timer("compile.currentBackend") request opts, (err, response, body)-> timer.done() - Metrics.inc "compile.currentBackend.response.#{response.statusCode}" + Metrics.inc "compile.currentBackend.response.#{response?.statusCode}" if err? logger.err err:err, project_id:project_id, url:opts?.url, "error making request to clsi" return callback(err) @@ -112,7 +112,7 @@ module.exports = ClsiManager = newBackend: (cb)-> startTime = new Date() ClsiManager._makeNewBackendRequest project_id, opts, (err, response, body)-> - Metrics.inc "compile.newBackend.response.#{response.statusCode}" + Metrics.inc "compile.newBackend.response.#{response?.statusCode}" cb(err, {response:response, body:body, finishTime:new Date() - startTime}) }, (err, results)-> timeDifference = results.newBackend?.finishTime - results.currentBackend?.finishTime From 05aedf1e099e92b9c792ab579a01d1165cdbef82 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 31 Jul 2018 16:54:09 +0100 Subject: [PATCH 013/104] add script to increase paid compile timeouts to 240s --- .../web/scripts/increase_compile_timeouts.js | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 services/web/scripts/increase_compile_timeouts.js diff --git a/services/web/scripts/increase_compile_timeouts.js b/services/web/scripts/increase_compile_timeouts.js new file mode 100644 index 0000000000..8aa451545a --- /dev/null +++ b/services/web/scripts/increase_compile_timeouts.js @@ -0,0 +1,64 @@ +const mongojs = require('../app/js/infrastructure/mongojs') +const { db } = mongojs +const async = require('async') +const minilist = require('minimist') + +const newTimeout = 240 +const oldTimeoutLimits = {$gt: 60, $lt: 240} + +const updateUser = function (user, callback) { + console.log(`Updating user ${user._id}`) + const update = { + $set: { + 'features.compileTimeout': newTimeout + } + } + db.users.update({ + _id: user._id, + 'features.compileTimeout': oldTimeoutLimits + }, update, callback) +} + +const updateUsers = (users, callback) => + async.eachLimit(users, ASYNC_LIMIT, updateUser, function (error) { + if (error) { + callback(error) + return + } + counter += users.length + console.log(`${counter} users updated`) + loopForUsers(callback) + }) + +var loopForUsers = callback => + db.users.find( + { 'features.compileTimeout': oldTimeoutLimits }, + { 'features.compileTimeout': 1 } + ).limit(FETCH_LIMIT, function (error, users) { + if (error) { + callback(error) + return + } + if (users.length === 0) { + console.log(`DONE (${counter} users updated)`) + return callback() + } + updateUsers(users, callback) + }) + +var counter = 0 +var run = () => + loopForUsers(function (error) { + if (error) { throw error } + process.exit() + }) + +let FETCH_LIMIT, ASYNC_LIMIT +var setup = function () { + let args = minilist(process.argv.slice(2)) + FETCH_LIMIT = (args.fetch) ? args.fetch : 100 + ASYNC_LIMIT = (args.async) ? args.async : 10 +} + +setup() +run() From 7c9c0fbf06dc2420866e9e5441763b058a21fa0d Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 2 Aug 2018 13:49:56 +0100 Subject: [PATCH 014/104] add --all option for increase_compile_timeouts script --- services/web/scripts/increase_compile_timeouts.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/services/web/scripts/increase_compile_timeouts.js b/services/web/scripts/increase_compile_timeouts.js index 8aa451545a..69323134e9 100644 --- a/services/web/scripts/increase_compile_timeouts.js +++ b/services/web/scripts/increase_compile_timeouts.js @@ -26,8 +26,13 @@ const updateUsers = (users, callback) => return } counter += users.length - console.log(`${counter} users updated`) - loopForUsers(callback) + console.log(`${counter} users updated`); + if (DO_ALL) { + return loopForUsers(callback) + } else { + console.log('run again to continue updating'); + return callback() + } }) var loopForUsers = callback => @@ -53,11 +58,15 @@ var run = () => process.exit() }) -let FETCH_LIMIT, ASYNC_LIMIT +let FETCH_LIMIT, ASYNC_LIMIT, DO_ALL var setup = function () { let args = minilist(process.argv.slice(2)) + // --fetch N get N users each time FETCH_LIMIT = (args.fetch) ? args.fetch : 100 + // --async M run M updates in parallel ASYNC_LIMIT = (args.async) ? args.async : 10 + // --all means run to completion + DO_ALL = (args.all) } setup() From d6ab6ce6eececf0627b4439d98fc9418e5550916 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 2 Aug 2018 14:06:51 +0100 Subject: [PATCH 015/104] improve --all switch to remove fetch limit --- .../web/scripts/increase_compile_timeouts.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/services/web/scripts/increase_compile_timeouts.js b/services/web/scripts/increase_compile_timeouts.js index 69323134e9..6889f26962 100644 --- a/services/web/scripts/increase_compile_timeouts.js +++ b/services/web/scripts/increase_compile_timeouts.js @@ -30,7 +30,7 @@ const updateUsers = (users, callback) => if (DO_ALL) { return loopForUsers(callback) } else { - console.log('run again to continue updating'); + console.log('*** run again to continue updating ***') return callback() } }) @@ -66,7 +66,19 @@ var setup = function () { // --async M run M updates in parallel ASYNC_LIMIT = (args.async) ? args.async : 10 // --all means run to completion - DO_ALL = (args.all) + if (args.all) { + if (args.fetch) { + console.error('error: do not use --fetch with --all') + process.exit(1) + } else { + DO_ALL = true + // if we are updating for all users then ignore the fetch limit. + FETCH_LIMIT = 0 + // A limit() value of 0 (i.e. .limit(0)) is equivalent to setting + // no limit. + // https://docs.mongodb.com/manual/reference/method/cursor.limit + } + } } setup() From b9597358a959b22e9b2b002b5f8ff1108b57e57e Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 2 Aug 2018 14:14:23 +0100 Subject: [PATCH 016/104] lint fix --- services/web/scripts/increase_compile_timeouts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/scripts/increase_compile_timeouts.js b/services/web/scripts/increase_compile_timeouts.js index 6889f26962..185c563277 100644 --- a/services/web/scripts/increase_compile_timeouts.js +++ b/services/web/scripts/increase_compile_timeouts.js @@ -26,7 +26,7 @@ const updateUsers = (users, callback) => return } counter += users.length - console.log(`${counter} users updated`); + console.log(`${counter} users updated`) if (DO_ALL) { return loopForUsers(callback) } else { From 3a80c3407891c828c227fecdfcee6aa6678dbcd4 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 26 Jul 2018 16:49:04 +0100 Subject: [PATCH 017/104] Basic label support (showing labels in the entries list; creating labels via a modal). --- .../project/editor/history/entriesListV2.pug | 6 +++ .../project/editor/history/toolbarV2.pug | 47 +++++++++++++++++-- .../ide/history/HistoryV2Manager.coffee | 39 +++++++++++---- .../HistoryV2AddLabelModalController.coffee | 28 +++++++++++ .../HistoryV2ToolbarController.coffee | 10 ++++ services/web/public/robots.txt | 21 ++++++++- .../stylesheets/app/editor/history-v2.less | 10 ++++ .../stylesheets/core/_common-variables.less | 1 + .../public/stylesheets/core/ol-variables.less | 1 + 9 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 services/web/public/coffee/ide/history/controllers/HistoryV2AddLabelModalController.coffee create mode 100644 services/web/public/coffee/ide/history/controllers/HistoryV2ToolbarController.coffee diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index fa7a90b20e..155ad6a3fd 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -129,6 +129,12 @@ script(type="text/ng-template", id="historyEntryTpl") time.history-entry-day(ng-if="::$ctrl.entry.meta.first_in_day") {{ ::$ctrl.entry.meta.end_ts | relativeDate }} .history-entry-details(ng-click="$ctrl.onSelect({ selectedEntry: $ctrl.entry })") + .history-entry-label( + ng-if="$ctrl.entry.labels.length > 0" + ng-repeat="label in ::$ctrl.entry.labels" + ) + i.fa.fa-tag + |  {{ label.comment }} ol.history-entry-changes li.history-entry-change( ng-repeat="pathname in ::$ctrl.entry.pathnames" diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index 799a7136f3..b53e54f95c 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -1,4 +1,5 @@ .history-toolbar( + ng-controller="HistoryV2ToolbarController" ng-if="ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME" ) span(ng-show="history.loadingFileTree") @@ -6,8 +7,46 @@ |    #{translate("loading")}... span(ng-show="!history.loadingFileTree") #{translate("browsing_project_as_of")}  time.history-toolbar-time {{ history.selection.updates[0].meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }} - .history-toolbar-btn( - ng-click="toggleHistoryViewMode();" + button.history-toolbar-btn( + ng-click="showAddLabelDialog();" + ng-disabled="history.loadingFileTree" ) - i.fa - | #{translate("compare_to_another_version")} \ No newline at end of file + i.fa.fa-tag + |  Label this version + button.history-toolbar-btn( + ng-click="toggleHistoryViewMode();" + ng-disabled="history.loadingFileTree" + ) + i.fa.fa-exchange + |  #{translate("compare_to_another_version")} + +script(type="text/ng-template", id="historyV2AddLabelModalTemplate") + form( + name="addLabelModalForm" + ng-submit="addLabelModalFormSubmit();" + novalidate + ) + .modal-header + h3 Label this version + .modal-body + .alert.alert-danger(ng-show="state.error.message") {{ state.error.message}} + .alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")} + .form-group + input.form-control( + type="text" + placeholder="New label name" + ng-model="inputs.labelName" + focus-on="open" + required + ) + .modal-footer + button.btn.btn-default( + type="button" + ng-disabled="state.inflight" + ng-click="$dismiss()" + ) #{translate("cancel")} + input.btn.btn-primary( + ng-disabled="addLabelModalForm.$invalid || state.inflight" + ng-value="state.inflight ? 'Adding label' : 'Add label'" + type="submit" + ) \ No newline at end of file diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index 7c8f476e39..e3623bb1fa 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -6,6 +6,8 @@ define [ "ide/history/controllers/HistoryV2ListController" "ide/history/controllers/HistoryV2DiffController" "ide/history/controllers/HistoryV2FileTreeController" + "ide/history/controllers/HistoryV2ToolbarController" + "ide/history/controllers/HistoryV2AddLabelModalController" "ide/history/directives/infiniteScroll" "ide/history/components/historyEntriesList" "ide/history/components/historyEntry" @@ -68,6 +70,7 @@ define [ toV: null } } + labels: null files: [] diff: null # When history.viewMode == HistoryViewModes.COMPARE selectedFile: null # When history.viewMode == HistoryViewModes.POINT_IN_TIME @@ -126,18 +129,25 @@ define [ BATCH_SIZE: 10 fetchNextBatchOfUpdates: () -> - url = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}" + updatesURL = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}" if @$scope.history.nextBeforeTimestamp? - url += "&before=#{@$scope.history.nextBeforeTimestamp}" + updatesURL += "&before=#{@$scope.history.nextBeforeTimestamp}" + @$scope.history.loading = true @$scope.history.loadingFileTree = true - @ide.$http - .get(url) - .then (response) => - { data } = response - @_loadUpdates(data.updates) - @$scope.history.nextBeforeTimestamp = data.nextBeforeTimestamp - if !data.nextBeforeTimestamp? + + requests = + updates: @ide.$http.get updatesURL + + if !@$scope.history.labels? + requests.labels = @ide.$http.get "/project/#{@ide.project_id}/labels" + + @ide.$q.all requests + .then (responses) => + updatesData = responses.updates.data + @_loadUpdates(updatesData.updates) + @$scope.history.nextBeforeTimestamp = updatesData.nextBeforeTimestamp + if !updatesData.nextBeforeTimestamp? @$scope.history.atEnd = true @$scope.history.loading = false @@ -199,6 +209,9 @@ define [ diff.loading = false diff.error = true + labelCurrentVersion: (labelComment) => + @_labelVersion labelComment, @$scope.history.selection.updates[0].toV + _parseDiff: (diff) -> if diff.binary return { binary: true } @@ -278,6 +291,14 @@ define [ else @autoSelectLastUpdate() + _labelVersion: (comment, version) -> + url = "/project/#{@$scope.project_id}/labels" + @ide.$http.post url, { + comment, + version, + _csrf: window.csrfToken + } + _perDocSummaryOfUpdates: (updates) -> # Track current_pathname -> original_pathname # create bare object for use as Map diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2AddLabelModalController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2AddLabelModalController.coffee new file mode 100644 index 0000000000..39b83d8998 --- /dev/null +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2AddLabelModalController.coffee @@ -0,0 +1,28 @@ +define [ + "base", +], (App) -> + App.controller "HistoryV2AddLabelModalController", ["$scope", "$modalInstance", "ide", ($scope, $modalInstance, ide) -> + $scope.inputs = + labelName: null + $scope.state = + inflight: false + error: false + + $modalInstance.opened.then () -> + $scope.$applyAsync () -> + $scope.$broadcast "open" + + $scope.addLabelModalFormSubmit = () -> + $scope.state.inflight = true + ide.historyManager.labelCurrentVersion $scope.inputs.labelName + .then (response) -> + $scope.state.inflight = false + $modalInstance.close() + .catch (response) -> + { data, status } = response + $scope.state.inflight = false + if status == 400 + $scope.state.error = { message: data } + else + $scope.state.error = true + ] \ No newline at end of file diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2ToolbarController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2ToolbarController.coffee new file mode 100644 index 0000000000..ed8dee63ec --- /dev/null +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2ToolbarController.coffee @@ -0,0 +1,10 @@ +define [ + "base", +], (App) -> + App.controller "HistoryV2ToolbarController", ["$scope", "$modal", "ide", ($scope, $modal, ide) -> + $scope.showAddLabelDialog = () -> + $modal.open( + templateUrl: "historyV2AddLabelModalTemplate" + controller: "HistoryV2AddLabelModalController" + ) + ] \ No newline at end of file diff --git a/services/web/public/robots.txt b/services/web/public/robots.txt index 77470cb39f..e241feebcc 100644 --- a/services/web/public/robots.txt +++ b/services/web/public/robots.txt @@ -1,2 +1,21 @@ +# robots.txt for https://www.sharelatex.com/ + User-agent: * -Disallow: / \ No newline at end of file +Disallow: /project/* +Disallow: /github/repos/* +Disallow: /recurly.com +Disallow: /user/password/set +Disallow: /learn-scripts/* +Allow: / + +User-Agent: AhrefsBot +Disallow: / + +User-Agent: XoviBot +Disallow: / + +User-Agent: RankSonicBot +Disallow: / + +User-Agent: SMTBot +Disallow: / diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index 9089ed1ae9..3e0d5f7ebc 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -53,6 +53,16 @@ color: #FFF; } } + + .history-entry-label { + color: @history-entry-label-color; + font-weight: bold; + margin-bottom: 3px; + .history-entry-selected & { + color: #FFF; + } + } + .history-entry-changes { .list-unstyled; margin-bottom: 3px; diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less index fefe12da73..ac2afdca45 100644 --- a/services/web/public/stylesheets/core/_common-variables.less +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -988,6 +988,7 @@ // v2 History @history-base-font-size : @font-size-small; @history-base-bg : @gray-lightest; +@history-entry-label-color : @red; @history-entry-day-bg : @gray; @history-entry-selected-bg : @red; @history-base-color : @gray-light; diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index 0e05ccdd68..40e53a0c71 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -277,6 +277,7 @@ // v2 History @history-base-font-size : @font-size-small; @history-base-bg : @ol-blue-gray-1; +@history-entry-label-color : @ol-blue; @history-entry-day-bg : @ol-blue-gray-2; @history-entry-selected-bg : @ol-green; @history-base-color : @ol-blue-gray-2; From 5a64313e245606315ab9c5c09625fcf6d81079fd Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Wed, 1 Aug 2018 12:17:29 +0100 Subject: [PATCH 018/104] Add label delete functionality; styles. --- .../project/editor/history/entriesListV2.pug | 12 +++++-- .../project/editor/history/toolbarV2.pug | 29 +++++++++++++++-- .../ide/history/HistoryV2Manager.coffee | 31 +++++++++++++------ .../components/historyEntriesList.coffee | 1 + .../history/components/historyEntry.coffee | 1 + .../HistoryV2AddLabelModalController.coffee | 3 +- ...HistoryV2DeleteLabelModalController.coffee | 23 ++++++++++++++ .../HistoryV2ListController.coffee | 11 +++++-- .../HistoryV2ToolbarController.coffee | 2 ++ .../stylesheets/app/editor/history-v2.less | 30 ++++++++++++++++-- .../stylesheets/core/_common-variables.less | 21 +++++++------ .../public/stylesheets/core/ol-variables.less | 21 +++++++------ 12 files changed, 149 insertions(+), 36 deletions(-) create mode 100644 services/web/public/coffee/ide/history/controllers/HistoryV2DeleteLabelModalController.coffee diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index 155ad6a3fd..ba7492663c 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -10,6 +10,7 @@ aside.change-list( load-initialize="ui.view == 'history'" is-loading="history.loading" on-entry-select="handleEntrySelect(selectedEntry)" + on-label-delete="handleLabelDelete(label)" ) aside.change-list( @@ -107,6 +108,7 @@ script(type="text/ng-template", id="historyEntriesListTpl") entry="entry" current-user="$ctrl.currentUser" on-select="$ctrl.onEntrySelect({ selectedEntry: selectedEntry })" + on-label-delete="$ctrl.onLabelDelete({ label: label })" ng-show="!$ctrl.isLoading" ) .loading(ng-show="$ctrl.isLoading") @@ -133,8 +135,14 @@ script(type="text/ng-template", id="historyEntryTpl") ng-if="$ctrl.entry.labels.length > 0" ng-repeat="label in ::$ctrl.entry.labels" ) - i.fa.fa-tag - |  {{ label.comment }} + span.history-entry-label-comment + i.fa.fa-tag + |  {{ label.comment }} + button.history-entry-label-delete-btn( + stop-propagation="click" + ng-click="$ctrl.onLabelDelete({ label: label })" + ) × + ol.history-entry-changes li.history-entry-change( ng-repeat="pathname in ::$ctrl.entry.pathnames" diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index b53e54f95c..1b8b733154 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -27,7 +27,7 @@ script(type="text/ng-template", id="historyV2AddLabelModalTemplate") novalidate ) .modal-header - h3 Label this version + h3 Add label .modal-body .alert.alert-danger(ng-show="state.error.message") {{ state.error.message}} .alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")} @@ -39,6 +39,9 @@ script(type="text/ng-template", id="historyV2AddLabelModalTemplate") focus-on="open" required ) + p.help-block(ng-if="update") + | A new label will be added as of + strong {{ update.meta.end_ts | formatDate:'ddd Do MMM YYYY, h:mm a' }} .modal-footer button.btn.btn-default( type="button" @@ -49,4 +52,26 @@ script(type="text/ng-template", id="historyV2AddLabelModalTemplate") ng-disabled="addLabelModalForm.$invalid || state.inflight" ng-value="state.inflight ? 'Adding label' : 'Add label'" type="submit" - ) \ No newline at end of file + ) + +script(type="text/ng-template", id="historyV2DeleteLabelModalTemplate") + .modal-header + h3 Delete label + .modal-body + .alert.alert-danger(ng-show="state.error.message") {{ state.error.message}} + .alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")} + p.help-block(ng-if="labelDetails") + | Are you sure you want to delete the following label: + strong "{{ labelDetails.comment }}" + | ? + .modal-footer + button.btn.btn-default( + type="button" + ng-disabled="state.inflight" + ng-click="$dismiss()" + ) #{translate("cancel")} + button.btn.btn-primary( + type="button" + ng-click="deleteLabel()" + ng-disabled="state.inflight" + ) {{ state.inflight ? 'Deleting label' : 'Delete label' }} \ No newline at end of file diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index e3623bb1fa..3a8640ac9a 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -8,6 +8,7 @@ define [ "ide/history/controllers/HistoryV2FileTreeController" "ide/history/controllers/HistoryV2ToolbarController" "ide/history/controllers/HistoryV2AddLabelModalController" + "ide/history/controllers/HistoryV2DeleteLabelModalController" "ide/history/directives/infiniteScroll" "ide/history/components/historyEntriesList" "ide/history/components/historyEntry" @@ -136,15 +137,9 @@ define [ @$scope.history.loading = true @$scope.history.loadingFileTree = true - requests = - updates: @ide.$http.get updatesURL - - if !@$scope.history.labels? - requests.labels = @ide.$http.get "/project/#{@ide.project_id}/labels" - - @ide.$q.all requests - .then (responses) => - updatesData = responses.updates.data + @ide.$http.get updatesURL + .then (response) => + updatesData = response.data @_loadUpdates(updatesData.updates) @$scope.history.nextBeforeTimestamp = updatesData.nextBeforeTimestamp if !updatesData.nextBeforeTimestamp? @@ -212,6 +207,24 @@ define [ labelCurrentVersion: (labelComment) => @_labelVersion labelComment, @$scope.history.selection.updates[0].toV + deleteLabel: (labelId) => + url = "/project/#{@$scope.project_id}/labels/#{labelId}" + + @ide.$http({ + url, + method: "DELETE" + headers: + "X-CSRF-Token": window.csrfToken + }).then (response) => + @_deleteLabelFromLocalCollection @$scope.history.updates, labelId + @_deleteLabelFromLocalCollection @$scope.history.selection, labelId + + + _deleteLabelFromLocalCollection: (collection, labelId) -> + for update in collection + update.labels = _.filter update.labels, (label) -> + label.id != labelId + _parseDiff: (diff) -> if diff.binary return { binary: true } diff --git a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee index 5022724714..82331258ec 100644 --- a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee @@ -14,6 +14,7 @@ define [ isLoading: "<" currentUser: "<" onEntrySelect: "&" + onLabelDelete: "&" controller: historyEntriesListController templateUrl: "historyEntriesListTpl" } diff --git a/services/web/public/coffee/ide/history/components/historyEntry.coffee b/services/web/public/coffee/ide/history/components/historyEntry.coffee index e2692b7dee..46018d3a32 100644 --- a/services/web/public/coffee/ide/history/components/historyEntry.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntry.coffee @@ -22,6 +22,7 @@ define [ entry: "<" currentUser: "<" onSelect: "&" + onLabelDelete: "&" controller: historyEntryController templateUrl: "historyEntryTpl" } \ No newline at end of file diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2AddLabelModalController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2AddLabelModalController.coffee index 39b83d8998..cbe72b0dce 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryV2AddLabelModalController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2AddLabelModalController.coffee @@ -1,7 +1,8 @@ define [ "base", ], (App) -> - App.controller "HistoryV2AddLabelModalController", ["$scope", "$modalInstance", "ide", ($scope, $modalInstance, ide) -> + App.controller "HistoryV2AddLabelModalController", ["$scope", "$modalInstance", "ide", "update", ($scope, $modalInstance, ide, update) -> + $scope.update = update $scope.inputs = labelName: null $scope.state = diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2DeleteLabelModalController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2DeleteLabelModalController.coffee new file mode 100644 index 0000000000..d03786c2c6 --- /dev/null +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2DeleteLabelModalController.coffee @@ -0,0 +1,23 @@ +define [ + "base", +], (App) -> + App.controller "HistoryV2DeleteLabelModalController", ["$scope", "$modalInstance", "ide", "labelDetails", ($scope, $modalInstance, ide, labelDetails) -> + $scope.labelDetails = labelDetails + $scope.state = + inflight: false + error: false + + $scope.deleteLabel = () -> + $scope.state.inflight = true + ide.historyManager.deleteLabel labelDetails.id + .then (response) -> + $scope.state.inflight = false + $modalInstance.close() + .catch (response) -> + { data, status } = response + $scope.state.inflight = false + if status == 400 + $scope.state.error = { message: data } + else + $scope.state.error = true + ] \ No newline at end of file diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee index eaf7fbc884..d63f9329ae 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee @@ -3,17 +3,24 @@ define [ "ide/history/util/displayNameForUser" ], (App, displayNameForUser) -> - App.controller "HistoryV2ListController", ["$scope", "ide", ($scope, ide) -> + App.controller "HistoryV2ListController", ["$scope", "$modal", "ide", ($scope, $modal, ide) -> $scope.hoveringOverListSelectors = false $scope.loadMore = () => ide.historyManager.fetchNextBatchOfUpdates() $scope.handleEntrySelect = (entry) -> - # $scope.$applyAsync () -> ide.historyManager.selectUpdate(entry) $scope.recalculateSelectedUpdates() + $scope.handleLabelDelete = (labelDetails) -> + $modal.open( + templateUrl: "historyV2DeleteLabelModalTemplate" + controller: "HistoryV2DeleteLabelModalController" + resolve: + labelDetails: () -> labelDetails + ) + $scope.recalculateSelectedUpdates = () -> beforeSelection = true afterSelection = false diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2ToolbarController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2ToolbarController.coffee index ed8dee63ec..3feb9f8a7d 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryV2ToolbarController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2ToolbarController.coffee @@ -6,5 +6,7 @@ define [ $modal.open( templateUrl: "historyV2AddLabelModalTemplate" controller: "HistoryV2AddLabelModalController" + resolve: + update: () -> $scope.history.selection.updates[0] ) ] \ No newline at end of file diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index 3e0d5f7ebc..9e099422c5 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -56,12 +56,38 @@ .history-entry-label { color: @history-entry-label-color; - font-weight: bold; + font-size: @font-size-small; margin-bottom: 3px; + margin-right: 10px; + &:last-of-type { + margin-right: 0; + } .history-entry-selected & { - color: #FFF; + color: @history-entry-selected-label-color; } } + .history-entry-label-comment, + .history-entry-label-delete-btn { + display: inline-block; + padding: 0 (@padding-xs-horizontal / 2) 1px @padding-xs-horizontal; + border: 0; + background-color: @history-entry-label-bg-color; + border-radius: 9999px 0 0 9999px; + .history-entry-selected & { + background-color: @history-entry-selected-label-bg-color; + } + } + .history-entry-label-delete-btn { + padding-left: (@padding-xs-horizontal / 2); + padding-right: @padding-xs-horizontal; + border-radius: 0 9999px 9999px 0; + &:hover { + background-color: darken(@history-entry-label-bg-color, 8%); + .history-entry-selected & { + background-color: darken(@history-entry-selected-label-bg-color, 8%); + } + } + } .history-entry-changes { .list-unstyled; diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less index ac2afdca45..aef1f2b5a2 100644 --- a/services/web/public/stylesheets/core/_common-variables.less +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -986,15 +986,18 @@ @sys-msg-border : 1px solid @common-border-color; // v2 History -@history-base-font-size : @font-size-small; -@history-base-bg : @gray-lightest; -@history-entry-label-color : @red; -@history-entry-day-bg : @gray; -@history-entry-selected-bg : @red; -@history-base-color : @gray-light; -@history-highlight-color : @gray; -@history-toolbar-bg-color : @toolbar-alt-bg-color; -@history-toolbar-color : @text-color; +@history-base-font-size : @font-size-small; +@history-base-bg : @gray-lightest; +@history-entry-label-bg-color : @red; +@history-entry-label-color : #FFF; +@history-entry-selected-label-bg-color : #FFF; +@history-entry-selected-label-color : @red; +@history-entry-day-bg : @gray; +@history-entry-selected-bg : @red; +@history-base-color : @gray-light; +@history-highlight-color : @gray; +@history-toolbar-bg-color : @toolbar-alt-bg-color; +@history-toolbar-color : @text-color; // Input suggestions @input-suggestion-v-offset : 6px; diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index 40e53a0c71..b75de6086d 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -275,15 +275,18 @@ // v2 History -@history-base-font-size : @font-size-small; -@history-base-bg : @ol-blue-gray-1; -@history-entry-label-color : @ol-blue; -@history-entry-day-bg : @ol-blue-gray-2; -@history-entry-selected-bg : @ol-green; -@history-base-color : @ol-blue-gray-2; -@history-highlight-color : @ol-type-color; -@history-toolbar-bg-color : @editor-toolbar-bg; -@history-toolbar-color : #FFF; +@history-base-font-size : @font-size-small; +@history-base-bg : @ol-blue-gray-1; +@history-entry-label-bg-color : @ol-blue; +@history-entry-label-color : #FFF; +@history-entry-selected-label-bg-color: #FFF; +@history-entry-selected-label-color : @ol-blue; +@history-entry-day-bg : @ol-blue-gray-2; +@history-entry-selected-bg : @ol-green; +@history-base-color : @ol-blue-gray-2; +@history-highlight-color : @ol-type-color; +@history-toolbar-bg-color : @editor-toolbar-bg; +@history-toolbar-color : #FFF; // System messages @sys-msg-background : @ol-blue; From d21f6ef62452ce560758834d4818774c091d129d Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Wed, 1 Aug 2018 16:03:45 +0100 Subject: [PATCH 019/104] Label handling in compare mode. --- .../project/editor/history/entriesListV2.pug | 15 ++++++++++++++- .../coffee/ide/history/HistoryV2Manager.coffee | 18 +++++++++++++----- .../controllers/HistoryListController.coffee | 10 +++++++++- .../stylesheets/app/editor/history-v2.less | 1 + 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index ba7492663c..4cbed71fda 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -66,6 +66,18 @@ aside.change-list( ) div.description(ng-click="select()") + div + .history-entry-label( + ng-if="update.labels.length > 0" + ng-repeat="label in update.labels" + ) + span.history-entry-label-comment + i.fa.fa-tag + |  {{ label.comment }} + button.history-entry-label-delete-btn( + stop-propagation="click" + ng-click="deleteLabel(label)" + ) × div.time {{ update.meta.end_ts | formatDate:'h:mm a' }} div.action.action-edited(ng-if="history.isV2 && update.pathnames.length > 0") | #{translate("file_action_edited")} @@ -133,7 +145,7 @@ script(type="text/ng-template", id="historyEntryTpl") .history-entry-details(ng-click="$ctrl.onSelect({ selectedEntry: $ctrl.entry })") .history-entry-label( ng-if="$ctrl.entry.labels.length > 0" - ng-repeat="label in ::$ctrl.entry.labels" + ng-repeat="label in $ctrl.entry.labels" ) span.history-entry-label-comment i.fa.fa-tag @@ -162,6 +174,7 @@ script(type="text/ng-template", id="historyEntryTpl") ng-if="::project_op.remove" ) #{translate("file_action_deleted")} span.history-entry-change-doc {{ ::$ctrl.getProjectOpDoc(project_op) }} + .history-entry-metadata time.history-entry-metadata-time {{ ::$ctrl.entry.meta.end_ts | formatDate:'h:mm a' }} span diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index 3a8640ac9a..a921cacb08 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -306,11 +306,19 @@ define [ _labelVersion: (comment, version) -> url = "/project/#{@$scope.project_id}/labels" - @ide.$http.post url, { - comment, - version, - _csrf: window.csrfToken - } + @ide.$http + .post url, { + comment, + version, + _csrf: window.csrfToken + } + .then (response) => + @_addLabelToLocalUpdate response.data + + _addLabelToLocalUpdate: (label) -> + localUpdate = _.find @$scope.history.updates, (update) -> update.toV == label.version + if localUpdate? + localUpdate.labels.push label _perDocSummaryOfUpdates: (updates) -> # Track current_pathname -> original_pathname diff --git a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee index 4b0786d259..e4bc2b541d 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee @@ -3,9 +3,17 @@ define [ "ide/history/util/displayNameForUser" ], (App, displayNameForUser) -> - App.controller "HistoryListController", ["$scope", "ide", ($scope, ide) -> + App.controller "HistoryListController", ["$scope", "$modal", "ide", ($scope, $modal, ide) -> $scope.hoveringOverListSelectors = false + $scope.deleteLabel = (labelDetails) -> + $modal.open( + templateUrl: "historyV2DeleteLabelModalTemplate" + controller: "HistoryV2DeleteLabelModalController" + resolve: + labelDetails: () -> labelDetails + ) + $scope.loadMore = () => ide.historyManager.fetchNextBatchOfUpdates() diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index 9e099422c5..7ed37d49b1 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -55,6 +55,7 @@ } .history-entry-label { + display: inline-block; color: @history-entry-label-color; font-size: @font-size-small; margin-bottom: 3px; From 8c50e4e9aec4fbe76d145cd14d4ecfe95d8047f7 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 2 Aug 2018 15:28:59 +0100 Subject: [PATCH 020/104] Add history label component. --- .../ide/history/components/historyLabel.coffee | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 services/web/public/coffee/ide/history/components/historyLabel.coffee diff --git a/services/web/public/coffee/ide/history/components/historyLabel.coffee b/services/web/public/coffee/ide/history/components/historyLabel.coffee new file mode 100644 index 0000000000..2394b76f8b --- /dev/null +++ b/services/web/public/coffee/ide/history/components/historyLabel.coffee @@ -0,0 +1,17 @@ +define [ + "base" +], (App) -> + historyLabelController = ($scope, $element, $attrs, $filter, _) -> + ctrl = @ + return + + App.component "historyLabel", { + bindings: + labelText: "<" + labelOwnerName: "<" + labelCreationDateTime: "<" + isOwnedByCurrentUser: "<" + onLabelDelete: "&" + controller: historyLabelController + templateUrl: "historyLabelTpl" + } \ No newline at end of file From a8ee87974653e9f679f8034573f9d0288c290b5e Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 2 Aug 2018 15:29:12 +0100 Subject: [PATCH 021/104] Use history label component; restrict label deletion to label owners. --- .../project/editor/history/entriesListV2.pug | 46 ++++++++++++++----- .../ide/history/HistoryV2Manager.coffee | 1 + .../components/historyEntriesList.coffee | 4 ++ .../history/components/historyEntry.coffee | 9 +++- .../HistoryV2ListController.coffee | 3 ++ .../stylesheets/app/editor/history-v2.less | 37 ++++++++++++--- 6 files changed, 82 insertions(+), 18 deletions(-) diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index 4cbed71fda..b96c74947d 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -5,10 +5,12 @@ aside.change-list( history-entries-list( entries="history.updates" current-user="user" + users="projectUsers" load-entries="loadMore()" load-disabled="history.loading || history.atEnd" load-initialize="ui.view == 'history'" is-loading="history.loading" + show-only-labelled="listConfig.showOnlyLabelled" on-entry-select="handleEntrySelect(selectedEntry)" on-label-delete="handleLabelDelete(label)" ) @@ -116,9 +118,10 @@ script(type="text/ng-template", id="historyEntriesListTpl") ) .infinite-scroll-inner history-entry( - ng-repeat="entry in $ctrl.entries" + ng-repeat="entry in $ctrl.entries | filter:$ctrl.shouldShowEntry" entry="entry" current-user="$ctrl.currentUser" + users="$ctrl.users" on-select="$ctrl.onEntrySelect({ selectedEntry: selectedEntry })" on-label-delete="$ctrl.onLabelDelete({ label: label })" ng-show="!$ctrl.isLoading" @@ -143,17 +146,14 @@ script(type="text/ng-template", id="historyEntryTpl") time.history-entry-day(ng-if="::$ctrl.entry.meta.first_in_day") {{ ::$ctrl.entry.meta.end_ts | relativeDate }} .history-entry-details(ng-click="$ctrl.onSelect({ selectedEntry: $ctrl.entry })") - .history-entry-label( - ng-if="$ctrl.entry.labels.length > 0" + history-label( ng-repeat="label in $ctrl.entry.labels" + label-text="label.comment" + label-owner-name="$ctrl.displayNameById(label.user_id)" + label-creation-date-time="label.created_at" + is-owned-by-current-user="label.user_id === $ctrl.currentUser.id" + on-label-delete="$ctrl.onLabelDelete({ label: label })" ) - span.history-entry-label-comment - i.fa.fa-tag - |  {{ label.comment }} - button.history-entry-label-delete-btn( - stop-propagation="click" - ng-click="$ctrl.onLabelDelete({ label: label })" - ) × ol.history-entry-changes li.history-entry-change( @@ -198,4 +198,28 @@ script(type="text/ng-template", id="historyEntryTpl") li.history-entry-metadata-user(ng-if="::$ctrl.entry.meta.users.length == 0") span.name( ng-style="$ctrl.getUserCSSStyle();" - ) #{translate("anonymous")} \ No newline at end of file + ) #{translate("anonymous")} + +script(type="text/ng-template", id="historyLabelTpl") + .history-entry-label( + ng-class="{ 'history-entry-label-own' : $ctrl.isOwnedByCurrentUser }" + ) + span.history-entry-label-comment( + tooltip-template="'historyLabelTooltipTpl'" + tooltip-placement="left" + ) + i.fa.fa-tag + |  {{ $ctrl.labelText }} + button.history-entry-label-delete-btn( + ng-if="$ctrl.isOwnedByCurrentUser" + stop-propagation="click" + ng-click="$ctrl.onLabelDelete()" + ) × + +script(type="text/ng-template", id="historyLabelTooltipTpl") + .history-label-tooltip + p.history-label-tooltip-title + i.fa.fa-tag + |  {{ $ctrl.labelText }} + p.history-label-tooltip-owner Created by {{ $ctrl.labelOwnerName }} + time.history-label-tooltip-datetime {{ labelCreationDateTime | formatDate }} \ No newline at end of file diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index a921cacb08..7ff41985f4 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -12,6 +12,7 @@ define [ "ide/history/directives/infiniteScroll" "ide/history/components/historyEntriesList" "ide/history/components/historyEntry" + "ide/history/components/historyLabel" "ide/history/components/historyFileTree" "ide/history/components/historyFileEntity" ], (moment, ColorManager, displayNameForUser, HistoryViewModes) -> diff --git a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee index 82331258ec..f872b5d72a 100644 --- a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee @@ -3,11 +3,14 @@ define [ ], (App) -> historyEntriesListController = ($scope, $element, $attrs) -> ctrl = @ + ctrl.shouldShowEntry = (entry) -> + !(ctrl.showOnlyLabelled and entry.labels.length == 0) return App.component "historyEntriesList", { bindings: entries: "<" + users: "<" loadEntries: "&" loadDisabled: "<" loadInitialize: "<" @@ -15,6 +18,7 @@ define [ currentUser: "<" onEntrySelect: "&" onLabelDelete: "&" + showOnlyLabelled: "<" controller: historyEntriesListController templateUrl: "historyEntriesListTpl" } diff --git a/services/web/public/coffee/ide/history/components/historyEntry.coffee b/services/web/public/coffee/ide/history/components/historyEntry.coffee index 46018d3a32..2995c5f481 100644 --- a/services/web/public/coffee/ide/history/components/historyEntry.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntry.coffee @@ -2,9 +2,15 @@ define [ "base" "ide/history/util/displayNameForUser" ], (App, displayNameForUser) -> - historyEntryController = ($scope, $element, $attrs) -> + historyEntryController = ($scope, $element, $attrs, $filter, _) -> ctrl = @ + _getUserById = (id) -> + _.find ctrl.users, (user) -> + curUserId = user?._id or user?.id + curUserId == id ctrl.displayName = displayNameForUser + ctrl.displayNameById = (id) -> + displayNameForUser(_getUserById(id)) ctrl.getProjectOpDoc = (projectOp) -> if projectOp.rename? then "#{ projectOp.rename.pathname} → #{ projectOp.rename.newPathname }" else if projectOp.add? then "#{ projectOp.add.pathname}" @@ -21,6 +27,7 @@ define [ bindings: entry: "<" currentUser: "<" + users: "<" onSelect: "&" onLabelDelete: "&" controller: historyEntryController diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee index d63f9329ae..f7c602733f 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee @@ -5,6 +5,9 @@ define [ App.controller "HistoryV2ListController", ["$scope", "$modal", "ide", ($scope, $modal, ide) -> $scope.hoveringOverListSelectors = false + $scope.listConfig = + showOnlyLabelled: false + $scope.projectUsers = $scope.project.members.concat $scope.project.owner $scope.loadMore = () => ide.historyManager.fetchNextBatchOfUpdates() diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index 7ed37d49b1..404609917a 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -60,24 +60,32 @@ font-size: @font-size-small; margin-bottom: 3px; margin-right: 10px; - &:last-of-type { - margin-right: 0; - } + white-space: nowrap; .history-entry-selected & { color: @history-entry-selected-label-color; } } .history-entry-label-comment, .history-entry-label-delete-btn { - display: inline-block; - padding: 0 (@padding-xs-horizontal / 2) 1px @padding-xs-horizontal; + padding: 0 @padding-xs-horizontal 1px @padding-xs-horizontal; border: 0; background-color: @history-entry-label-bg-color; - border-radius: 9999px 0 0 9999px; .history-entry-selected & { background-color: @history-entry-selected-label-bg-color; } } + .history-entry-label-comment { + display: block; + float: left; + border-radius: 9999px; + max-width: 190px; + overflow: hidden; + text-overflow: ellipsis; + .history-entry-label-own & { + padding-right: (@padding-xs-horizontal / 2); + border-radius: 9999px 0 0 9999px; + } + } .history-entry-label-delete-btn { padding-left: (@padding-xs-horizontal / 2); padding-right: @padding-xs-horizontal; @@ -90,6 +98,23 @@ } } + .history-label-tooltip { + white-space: normal; + padding: (@line-height-computed / 4); + text-align: left; + } + .history-label-tooltip-title, + .history-label-tooltip-owner, + .history-label-tooltip-datetime { + margin: 0 0 (@line-height-computed / 4) 0; + } + .history-label-tooltip-title { + font-weight: bold; + } + .history-label-tooltip-datetime { + margin-bottom: 0; + } + .history-entry-changes { .list-unstyled; margin-bottom: 3px; From dc235b91d98ed23eb90c72c116e15b85a10169aa Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 2 Aug 2018 15:46:42 +0100 Subject: [PATCH 022/104] Fix some issues with the label tooltip in compare mode. --- .../web/app/views/project/editor/history.pug | 26 ++++++++++ .../project/editor/history/entriesListV2.pug | 52 +++++++------------ .../controllers/HistoryListController.coffee | 12 ++++- 3 files changed, 55 insertions(+), 35 deletions(-) diff --git a/services/web/app/views/project/editor/history.pug b/services/web/app/views/project/editor/history.pug index a7a52d2927..f0d22136b5 100644 --- a/services/web/app/views/project/editor/history.pug +++ b/services/web/app/views/project/editor/history.pug @@ -67,3 +67,29 @@ script(type="text/ng-template", id="historyRestoreDiffModalTemplate") ) span(ng-show="!state.inflight") #{translate("restore")} span(ng-show="state.inflight") #{translate("restoring")} ... + + +script(type="text/ng-template", id="historyLabelTpl") + .history-entry-label( + ng-class="{ 'history-entry-label-own' : $ctrl.isOwnedByCurrentUser }" + ) + span.history-entry-label-comment( + tooltip-append-to-body="true" + tooltip-template="'historyLabelTooltipTpl'" + tooltip-placement="left" + ) + i.fa.fa-tag + |  {{ $ctrl.labelText }} + button.history-entry-label-delete-btn( + ng-if="$ctrl.isOwnedByCurrentUser" + stop-propagation="click" + ng-click="$ctrl.onLabelDelete()" + ) × + +script(type="text/ng-template", id="historyLabelTooltipTpl") + .history-label-tooltip + p.history-label-tooltip-title + i.fa.fa-tag + |  {{ $ctrl.labelText }} + p.history-label-tooltip-owner Created by {{ $ctrl.labelOwnerName }} + time.history-label-tooltip-datetime {{ labelCreationDateTime | formatDate }} diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index b96c74947d..52da25c3b5 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -69,17 +69,25 @@ aside.change-list( div.description(ng-click="select()") div - .history-entry-label( - ng-if="update.labels.length > 0" + history-label( ng-repeat="label in update.labels" + label-text="label.comment" + label-owner-name="getDisplayNameById(label.user_id)" + label-creation-date-time="label.created_at" + is-owned-by-current-user="label.user_id === $ctrl.currentUser.id" + on-label-delete="deleteLabel(label)" ) - span.history-entry-label-comment - i.fa.fa-tag - |  {{ label.comment }} - button.history-entry-label-delete-btn( - stop-propagation="click" - ng-click="deleteLabel(label)" - ) × + //- .history-entry-label( + //- ng-if="update.labels.length > 0" + //- ng-repeat="label in update.labels" + //- ) + //- span.history-entry-label-comment + //- i.fa.fa-tag + //- |  {{ label.comment }} + //- button.history-entry-label-delete-btn( + //- stop-propagation="click" + //- ng-click="deleteLabel(label)" + //- ) × div.time {{ update.meta.end_ts | formatDate:'h:mm a' }} div.action.action-edited(ng-if="history.isV2 && update.pathnames.length > 0") | #{translate("file_action_edited")} @@ -198,28 +206,4 @@ script(type="text/ng-template", id="historyEntryTpl") li.history-entry-metadata-user(ng-if="::$ctrl.entry.meta.users.length == 0") span.name( ng-style="$ctrl.getUserCSSStyle();" - ) #{translate("anonymous")} - -script(type="text/ng-template", id="historyLabelTpl") - .history-entry-label( - ng-class="{ 'history-entry-label-own' : $ctrl.isOwnedByCurrentUser }" - ) - span.history-entry-label-comment( - tooltip-template="'historyLabelTooltipTpl'" - tooltip-placement="left" - ) - i.fa.fa-tag - |  {{ $ctrl.labelText }} - button.history-entry-label-delete-btn( - ng-if="$ctrl.isOwnedByCurrentUser" - stop-propagation="click" - ng-click="$ctrl.onLabelDelete()" - ) × - -script(type="text/ng-template", id="historyLabelTooltipTpl") - .history-label-tooltip - p.history-label-tooltip-title - i.fa.fa-tag - |  {{ $ctrl.labelText }} - p.history-label-tooltip-owner Created by {{ $ctrl.labelOwnerName }} - time.history-label-tooltip-datetime {{ labelCreationDateTime | formatDate }} \ No newline at end of file + ) #{translate("anonymous")} \ No newline at end of file diff --git a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee index e4bc2b541d..8257eebd8c 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee @@ -5,7 +5,17 @@ define [ App.controller "HistoryListController", ["$scope", "$modal", "ide", ($scope, $modal, ide) -> $scope.hoveringOverListSelectors = false - + + projectUsers = $scope.project.members.concat $scope.project.owner + console.log projectUsers + _getUserById = (id) -> + _.find projectUsers, (user) -> + curUserId = user?._id or user?.id + curUserId == id + + $scope.getDisplayNameById = (id) -> + displayNameForUser(_getUserById(id)) + $scope.deleteLabel = (labelDetails) -> $modal.open( templateUrl: "historyV2DeleteLabelModalTemplate" From 2c2bb4c130da809fdfe84833c941ae8d1386248d Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 2 Aug 2018 16:20:01 +0100 Subject: [PATCH 023/104] Add i18n; avoid injecting unneeded dependencies. --- .../web/app/views/project/editor/history.pug | 2 +- .../project/editor/history/entriesListV2.pug | 11 ----------- .../views/project/editor/history/toolbarV2.pug | 16 ++++++++-------- .../ide/history/components/historyEntry.coffee | 2 +- 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/services/web/app/views/project/editor/history.pug b/services/web/app/views/project/editor/history.pug index f0d22136b5..6281bbee49 100644 --- a/services/web/app/views/project/editor/history.pug +++ b/services/web/app/views/project/editor/history.pug @@ -91,5 +91,5 @@ script(type="text/ng-template", id="historyLabelTooltipTpl") p.history-label-tooltip-title i.fa.fa-tag |  {{ $ctrl.labelText }} - p.history-label-tooltip-owner Created by {{ $ctrl.labelOwnerName }} + p.history-label-tooltip-owner #{translate("history_label_created_by")} {{ $ctrl.labelOwnerName }} time.history-label-tooltip-datetime {{ labelCreationDateTime | formatDate }} diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index 52da25c3b5..e4ad48caba 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -77,17 +77,6 @@ aside.change-list( is-owned-by-current-user="label.user_id === $ctrl.currentUser.id" on-label-delete="deleteLabel(label)" ) - //- .history-entry-label( - //- ng-if="update.labels.length > 0" - //- ng-repeat="label in update.labels" - //- ) - //- span.history-entry-label-comment - //- i.fa.fa-tag - //- |  {{ label.comment }} - //- button.history-entry-label-delete-btn( - //- stop-propagation="click" - //- ng-click="deleteLabel(label)" - //- ) × div.time {{ update.meta.end_ts | formatDate:'h:mm a' }} div.action.action-edited(ng-if="history.isV2 && update.pathnames.length > 0") | #{translate("file_action_edited")} diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index 1b8b733154..76d4ed1494 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -12,7 +12,7 @@ ng-disabled="history.loadingFileTree" ) i.fa.fa-tag - |  Label this version + |  #{translate("history_label_this_version")} button.history-toolbar-btn( ng-click="toggleHistoryViewMode();" ng-disabled="history.loadingFileTree" @@ -27,20 +27,20 @@ script(type="text/ng-template", id="historyV2AddLabelModalTemplate") novalidate ) .modal-header - h3 Add label + h3 #{translate("history_add_label")} .modal-body .alert.alert-danger(ng-show="state.error.message") {{ state.error.message}} .alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")} .form-group input.form-control( type="text" - placeholder="New label name" + placeholder=translate("history_new_label_name") ng-model="inputs.labelName" focus-on="open" required ) p.help-block(ng-if="update") - | A new label will be added as of + | #{translate("history_new_label_added_at")} strong {{ update.meta.end_ts | formatDate:'ddd Do MMM YYYY, h:mm a' }} .modal-footer button.btn.btn-default( @@ -50,18 +50,18 @@ script(type="text/ng-template", id="historyV2AddLabelModalTemplate") ) #{translate("cancel")} input.btn.btn-primary( ng-disabled="addLabelModalForm.$invalid || state.inflight" - ng-value="state.inflight ? 'Adding label' : 'Add label'" + ng-value="state.inflight ? '" + translate("history_adding_label") + "' : '" + translate("history_add_label") + "'" type="submit" ) script(type="text/ng-template", id="historyV2DeleteLabelModalTemplate") .modal-header - h3 Delete label + h3 #{translate("history_delete_label")} .modal-body .alert.alert-danger(ng-show="state.error.message") {{ state.error.message}} .alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")} p.help-block(ng-if="labelDetails") - | Are you sure you want to delete the following label: + | #{translate("history_are_you_sure_delete_label")} strong "{{ labelDetails.comment }}" | ? .modal-footer @@ -74,4 +74,4 @@ script(type="text/ng-template", id="historyV2DeleteLabelModalTemplate") type="button" ng-click="deleteLabel()" ng-disabled="state.inflight" - ) {{ state.inflight ? 'Deleting label' : 'Delete label' }} \ No newline at end of file + ) {{ state.inflight ? '#{translate("history_deleting_label")}' : '#{translate("history_delete_label")}' }} diff --git a/services/web/public/coffee/ide/history/components/historyEntry.coffee b/services/web/public/coffee/ide/history/components/historyEntry.coffee index 2995c5f481..eb7f1e7266 100644 --- a/services/web/public/coffee/ide/history/components/historyEntry.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntry.coffee @@ -2,7 +2,7 @@ define [ "base" "ide/history/util/displayNameForUser" ], (App, displayNameForUser) -> - historyEntryController = ($scope, $element, $attrs, $filter, _) -> + historyEntryController = ($scope, $element, $attrs, _) -> ctrl = @ _getUserById = (id) -> _.find ctrl.users, (user) -> From f418929dcfe58c034e3a7d8436cb3b117d2182c9 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 2 Aug 2018 16:23:47 +0100 Subject: [PATCH 024/104] Adjust i18n. --- services/web/app/views/project/editor/history/toolbarV2.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index 76d4ed1494..b4afe439b3 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -61,7 +61,7 @@ script(type="text/ng-template", id="historyV2DeleteLabelModalTemplate") .alert.alert-danger(ng-show="state.error.message") {{ state.error.message}} .alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")} p.help-block(ng-if="labelDetails") - | #{translate("history_are_you_sure_delete_label")} + | #{translate("history_are_you_sure_delete_label")}: strong "{{ labelDetails.comment }}" | ? .modal-footer From f605e79fa5ff0b9a2bfc3f21890bc85b5134330b Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 2 Aug 2018 16:33:45 +0100 Subject: [PATCH 025/104] Remove changes to robots.txt. --- services/web/public/robots.txt | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/services/web/public/robots.txt b/services/web/public/robots.txt index e241feebcc..77470cb39f 100644 --- a/services/web/public/robots.txt +++ b/services/web/public/robots.txt @@ -1,21 +1,2 @@ -# robots.txt for https://www.sharelatex.com/ - User-agent: * -Disallow: /project/* -Disallow: /github/repos/* -Disallow: /recurly.com -Disallow: /user/password/set -Disallow: /learn-scripts/* -Allow: / - -User-Agent: AhrefsBot -Disallow: / - -User-Agent: XoviBot -Disallow: / - -User-Agent: RankSonicBot -Disallow: / - -User-Agent: SMTBot -Disallow: / +Disallow: / \ No newline at end of file From 80eeaaaaea9e5b5b44a83b0501087f037741dbb0 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 2 Aug 2018 17:19:16 +0100 Subject: [PATCH 026/104] Update web acceptance tests. --- .../coffee/ide/history/HistoryV2ManagerTests.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee b/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee index 5d5ba13609..725befb721 100644 --- a/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee +++ b/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee @@ -23,6 +23,7 @@ define ['ide/history/HistoryV2Manager'], (HistoryV2Manager) -> toV: null } } + labels: null diff: null files: [] selectedFile: null From d8c9a966197ac88f0caac7656c300af54bb96cbc Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 31 Jul 2018 15:53:05 +0100 Subject: [PATCH 027/104] If we're creating v1 accounts, don't allow login for users already linked up --- .../AuthenticationController.coffee | 44 ++++++++++++------- .../AuthenticationControllerTests.coffee | 2 +- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee index 832f01043f..951506891f 100644 --- a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee +++ b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee @@ -1,6 +1,5 @@ AuthenticationManager = require ("./AuthenticationManager") LoginRateLimiter = require("../Security/LoginRateLimiter") -UserGetter = require "../User/UserGetter" UserUpdater = require "../User/UserUpdater" Metrics = require('metrics-sharelatex') logger = require("logger-sharelatex") @@ -64,7 +63,10 @@ module.exports = AuthenticationController = if user # `user` is either a user object or false AuthenticationController.finishLogin(user, req, res, next) else - res.json message: info + if info.redir? + res.json {redir: info.redir} + else + res.json message: info )(req, res, next) finishLogin: (user, req, res, next) -> @@ -81,20 +83,30 @@ module.exports = AuthenticationController = doPassportLogin: (req, username, password, done) -> email = username.toLowerCase() - LoginRateLimiter.processLoginRequest email, (err, isAllowed)-> - return done(err) if err? - if !isAllowed - logger.log email:email, "too many login requests" - return done(null, null, {text: req.i18n.translate("to_many_login_requests_2_mins"), type: 'error'}) - AuthenticationManager.authenticate email: email, password, (error, user) -> - return done(error) if error? - if user? - # async actions - return done(null, user) - else - AuthenticationController._recordFailedLogin() - logger.log email: email, "failed log in" - return done(null, false, {text: req.i18n.translate("email_or_password_wrong_try_again"), type: 'error'}) + Modules = require "../../infrastructure/Modules" + Modules.hooks.fire 'preDoPassportLogin', email, (err, infoList) -> + return next(err) if err? + info = infoList.find((i) => i?) + if info? + return done(null, false, info) + LoginRateLimiter.processLoginRequest email, (err, isAllowed)-> + return done(err) if err? + if !isAllowed + logger.log email:email, "too many login requests" + return done(null, null, {text: req.i18n.translate("to_many_login_requests_2_mins"), type: 'error'}) + AuthenticationManager.authenticate email: email, password, (error, user) -> + return done(error) if error? + if user? + # async actions + return done(null, user) + else + AuthenticationController._recordFailedLogin() + logger.log email: email, "failed log in" + return done( + null, + false, + {text: req.i18n.translate("email_or_password_wrong_try_again"), type: 'error'} + ) _loginAsyncHandlers: (req, user) -> UserHandler.setupLoginData(user, ()->) diff --git a/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee b/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee index 8482c28e04..030c1cd0df 100644 --- a/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee +++ b/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee @@ -15,7 +15,6 @@ describe "AuthenticationController", -> tk.freeze(Date.now()) @AuthenticationController = SandboxedModule.require modulePath, requires: "./AuthenticationManager": @AuthenticationManager = {} - "../User/UserGetter" : @UserGetter = {} "../User/UserUpdater" : @UserUpdater = {} "metrics-sharelatex": @Metrics = { inc: sinon.stub() } "../Security/LoginRateLimiter": @LoginRateLimiter = { processLoginRequest:sinon.stub(), recordSuccessfulLogin:sinon.stub() } @@ -29,6 +28,7 @@ describe "AuthenticationController", -> trackSession: sinon.stub() untrackSession: sinon.stub() revokeAllUserSessions: sinon.stub().callsArgWith(1, null) + "../../infrastructure/Modules": {hooks: {fire: sinon.stub().callsArgWith(2, null, [])}} @user = _id: ObjectId() email: @email = "USER@example.com" From 05df55c89e183baffa736fb4fd41210bf63b4690 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Fri, 3 Aug 2018 16:10:50 +0100 Subject: [PATCH 028/104] Add a unit test for the `preDoPassportLogin` module hook. --- .../AuthenticationControllerTests.coffee | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee b/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee index 030c1cd0df..ae8fedc9c8 100644 --- a/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee +++ b/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee @@ -28,7 +28,7 @@ describe "AuthenticationController", -> trackSession: sinon.stub() untrackSession: sinon.stub() revokeAllUserSessions: sinon.stub().callsArgWith(1, null) - "../../infrastructure/Modules": {hooks: {fire: sinon.stub().callsArgWith(2, null, [])}} + "../../infrastructure/Modules": @Modules = {hooks: {fire: sinon.stub().callsArgWith(2, null, [])}} @user = _id: ObjectId() email: @email = "USER@example.com" @@ -214,6 +214,7 @@ describe "AuthenticationController", -> beforeEach -> @AuthenticationController._recordFailedLogin = sinon.stub() @AuthenticationController._recordSuccessfulLogin = sinon.stub() + @Modules.hooks.fire = sinon.stub().callsArgWith(2, null, []) # @AuthenticationController.establishUserSession = sinon.stub().callsArg(2) @req.body = email: @email @@ -222,6 +223,17 @@ describe "AuthenticationController", -> postLoginRedirect: "/path/to/redir/to" @cb = sinon.stub() + describe "when the preDoPassportLogin hooks produce an info object", -> + beforeEach -> + @Modules.hooks.fire = sinon.stub().callsArgWith(2, null, [null, {redir: '/somewhere'}, null]) + + it "should stop early and call done with this info object", (done) -> + @AuthenticationController.doPassportLogin(@req, @req.body.email, @req.body.password, @cb) + @cb.callCount.should.equal 1 + @cb.calledWith(null, false, {redir: '/somewhere'}).should.equal true + @LoginRateLimiter.processLoginRequest.callCount.should.equal 0 + done() + describe "when the users rate limit", -> beforeEach -> From 17a63258c6ee4ea24e5d10bad4c3bf85d91fdf01 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Sat, 4 Aug 2018 17:30:24 +0100 Subject: [PATCH 029/104] changed newsletter to use mailchimp --- .../Newsletter/NewsletterManager.coffee | 51 ++++++++++++------- services/web/config/settings.defaults.coffee | 6 +-- services/web/package.json | 1 + 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee index 8d7eee43bf..7baa317632 100644 --- a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee +++ b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee @@ -1,37 +1,52 @@ async = require('async') -Request = require('request') logger = require 'logger-sharelatex' Settings = require 'settings-sharelatex' +crypto = require('crypto') +Mailchimp = require('mailchimp-api-v3') +mailchimp = new Mailchimp(Settings.mailchimp?.api_key) if Settings.mailchimp? module.exports = subscribe: (user, callback = () ->)-> - if !Settings.markdownmail? + if !Settings.mailchimp? logger.warn "No newsletter provider configured so not subscribing user" return callback() - logger.log user:user, email:user.email, "trying to subscribe user to the mailing list" options = buildOptions(user, true) - Request.post options, (err, response, body)-> - logger.log body:body, user:user, "finished attempting to subscribe the user to the news letter" + logger.log options:options, user:user, email:user.email, "trying to subscribe user to the mailing list" + mailchimp.request options, (err)-> + if err? + logger.err err:err, "error subscribing person to newsletter" + else + logger.log user:user, "finished subscribing user to the newsletter" callback(err) unsubscribe: (user, callback = () ->)-> - if !Settings.markdownmail? + if !Settings.mailchimp? logger.warn "No newsletter provider configured so not unsubscribing user" return callback() logger.log user:user, email:user.email, "trying to unsubscribe user to the mailing list" options = buildOptions(user, false) - Request.post options, (err, response, body)-> - logger.log err:err, body:body, email:user.email, "compled newsletter unsubscribe attempt" + mailchimp.request options, (err)-> + if err? + logger.err err:err, "error unsubscribing person to newsletter" + else + logger.log user:user, "finished unsubscribing user to the newsletter" callback(err) +hashEmail = (email)-> + crypto.createHash('md5').update(email.toLowerCase()).digest("hex") + buildOptions = (user, is_subscribed)-> - options = - json: - secret_token: Settings.markdownmail.secret - name: "#{user.first_name} #{user.last_name}" - email: user.email - subscriber_list_id: Settings.markdownmail.list_id - is_subscribed: is_subscribed - url: "https://www.markdownmail.io/lists/subscribe" - timeout: 30 * 1000 - return options \ No newline at end of file + status = if is_subscribed then "subscribed" else "unsubscribed" + subscriber_hash = hashEmail(user.email) + opts = + method: "PUT" + path: "/lists/#{Settings.mailchimp?.list_id}/members/#{subscriber_hash}" + body: + status_if_new: status + status: status + email_address:user.email + merge_fields: + FNAME: user.first_name + LNAME: user.last_name + return opts + diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 1306ce91c2..827f4f2959 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -277,10 +277,10 @@ module.exports = settings = # Third party services # -------------------- # - # ShareLaTeX's regular newsletter is managed by Markdown mail. Add your + # ShareLaTeX's regular newsletter is managed by mailchimp. Add your # credentials here to integrate with this. - # markdownmail: - # secret: "" + # mailchimp: + # api_key: "" # list_id: "" # # Fill in your unique token from various analytics services to enable diff --git a/services/web/package.json b/services/web/package.json index ed0eb06455..1dce008612 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -59,6 +59,7 @@ "lodash": "^4.13.1", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master", "lynx": "0.1.1", + "mailchimp-api-v3": "^1.12.0", "marked": "^0.3.5", "method-override": "^2.3.3", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.7.1", From 7da8a926bbacdf6c04b0aca106fad640f18256fc Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 6 Aug 2018 10:52:10 +0100 Subject: [PATCH 030/104] increase bodyparser limit to 64kb 16kb is not enough for bibtex files with more escaping. --- services/web/app/coffee/infrastructure/Server.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/coffee/infrastructure/Server.coffee b/services/web/app/coffee/infrastructure/Server.coffee index cf2757c80e..bf9d6652f4 100644 --- a/services/web/app/coffee/infrastructure/Server.coffee +++ b/services/web/app/coffee/infrastructure/Server.coffee @@ -68,7 +68,7 @@ Modules.loadViewIncludes app app.use bodyParser.urlencoded({ extended: true, limit: "2mb"}) # Make sure we can process the max doc length plus some overhead for JSON encoding -app.use bodyParser.json({limit: Settings.max_doc_length + 16 * 1024}) # 16kb overhead +app.use bodyParser.json({limit: Settings.max_doc_length + 64 * 1024}) # 64kb overhead app.use multer(dest: Settings.path.uploadFolder) app.use methodOverride() From bd6dcc007b7c416ab68db08848f0d42ab1fcff28 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 6 Aug 2018 11:00:45 +0100 Subject: [PATCH 031/104] Remove prototype code. --- .../web/app/views/project/editor/history/entriesListV2.pug | 3 +-- .../coffee/ide/history/components/historyEntriesList.coffee | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index e4ad48caba..55d86c434f 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -10,7 +10,6 @@ aside.change-list( load-disabled="history.loading || history.atEnd" load-initialize="ui.view == 'history'" is-loading="history.loading" - show-only-labelled="listConfig.showOnlyLabelled" on-entry-select="handleEntrySelect(selectedEntry)" on-label-delete="handleLabelDelete(label)" ) @@ -115,7 +114,7 @@ script(type="text/ng-template", id="historyEntriesListTpl") ) .infinite-scroll-inner history-entry( - ng-repeat="entry in $ctrl.entries | filter:$ctrl.shouldShowEntry" + ng-repeat="entry in $ctrl.entries" entry="entry" current-user="$ctrl.currentUser" users="$ctrl.users" diff --git a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee index f872b5d72a..934ca304fe 100644 --- a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee @@ -3,8 +3,6 @@ define [ ], (App) -> historyEntriesListController = ($scope, $element, $attrs) -> ctrl = @ - ctrl.shouldShowEntry = (entry) -> - !(ctrl.showOnlyLabelled and entry.labels.length == 0) return App.component "historyEntriesList", { @@ -18,7 +16,6 @@ define [ currentUser: "<" onEntrySelect: "&" onLabelDelete: "&" - showOnlyLabelled: "<" controller: historyEntriesListController templateUrl: "historyEntriesListTpl" } From e0707253a773146df8738b3fde56acd811bdfd81 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 6 Aug 2018 11:03:15 +0100 Subject: [PATCH 032/104] Remove debug lines and unneeded HTML. --- .../project/editor/history/entriesListV2.pug | 17 ++++++++--------- .../controllers/HistoryListController.coffee | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index 55d86c434f..337b5e33ce 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -67,15 +67,14 @@ aside.change-list( ) div.description(ng-click="select()") - div - history-label( - ng-repeat="label in update.labels" - label-text="label.comment" - label-owner-name="getDisplayNameById(label.user_id)" - label-creation-date-time="label.created_at" - is-owned-by-current-user="label.user_id === $ctrl.currentUser.id" - on-label-delete="deleteLabel(label)" - ) + history-label( + ng-repeat="label in update.labels" + label-text="label.comment" + label-owner-name="getDisplayNameById(label.user_id)" + label-creation-date-time="label.created_at" + is-owned-by-current-user="label.user_id === $ctrl.currentUser.id" + on-label-delete="deleteLabel(label)" + ) div.time {{ update.meta.end_ts | formatDate:'h:mm a' }} div.action.action-edited(ng-if="history.isV2 && update.pathnames.length > 0") | #{translate("file_action_edited")} diff --git a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee index 8257eebd8c..90c8142c6c 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee @@ -7,7 +7,7 @@ define [ $scope.hoveringOverListSelectors = false projectUsers = $scope.project.members.concat $scope.project.owner - console.log projectUsers + _getUserById = (id) -> _.find projectUsers, (user) -> curUserId = user?._id or user?.id From b0261970fc3d88abe9314d4bf1dcd6cc68806712 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 6 Aug 2018 11:26:22 +0100 Subject: [PATCH 033/104] Make label removal also work in compare mode. --- .../web/app/views/project/editor/history.pug | 23 ++++++++++++++++++ .../project/editor/history/entriesListV2.pug | 2 +- .../project/editor/history/toolbarV2.pug | 24 +------------------ .../history/components/historyEntry.coffee | 3 +++ .../controllers/HistoryListController.coffee | 3 +++ 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/services/web/app/views/project/editor/history.pug b/services/web/app/views/project/editor/history.pug index 6281bbee49..2f3f317561 100644 --- a/services/web/app/views/project/editor/history.pug +++ b/services/web/app/views/project/editor/history.pug @@ -93,3 +93,26 @@ script(type="text/ng-template", id="historyLabelTooltipTpl") |  {{ $ctrl.labelText }} p.history-label-tooltip-owner #{translate("history_label_created_by")} {{ $ctrl.labelOwnerName }} time.history-label-tooltip-datetime {{ labelCreationDateTime | formatDate }} + + +script(type="text/ng-template", id="historyV2DeleteLabelModalTemplate") + .modal-header + h3 #{translate("history_delete_label")} + .modal-body + .alert.alert-danger(ng-show="state.error.message") {{ state.error.message}} + .alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")} + p.help-block(ng-if="labelDetails") + | #{translate("history_are_you_sure_delete_label")} + strong "{{ labelDetails.comment }}" + | ? + .modal-footer + button.btn.btn-default( + type="button" + ng-disabled="state.inflight" + ng-click="$dismiss()" + ) #{translate("cancel")} + button.btn.btn-primary( + type="button" + ng-click="deleteLabel()" + ng-disabled="state.inflight" + ) {{ state.inflight ? '#{translate("history_deleting_label")}' : '#{translate("history_delete_label")}' }} diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index 337b5e33ce..e08de0effd 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -72,7 +72,7 @@ aside.change-list( label-text="label.comment" label-owner-name="getDisplayNameById(label.user_id)" label-creation-date-time="label.created_at" - is-owned-by-current-user="label.user_id === $ctrl.currentUser.id" + is-owned-by-current-user="label.user_id === user.id" on-label-delete="deleteLabel(label)" ) div.time {{ update.meta.end_ts | formatDate:'h:mm a' }} diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index b4afe439b3..2a4c222bad 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -52,26 +52,4 @@ script(type="text/ng-template", id="historyV2AddLabelModalTemplate") ng-disabled="addLabelModalForm.$invalid || state.inflight" ng-value="state.inflight ? '" + translate("history_adding_label") + "' : '" + translate("history_add_label") + "'" type="submit" - ) - -script(type="text/ng-template", id="historyV2DeleteLabelModalTemplate") - .modal-header - h3 #{translate("history_delete_label")} - .modal-body - .alert.alert-danger(ng-show="state.error.message") {{ state.error.message}} - .alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")} - p.help-block(ng-if="labelDetails") - | #{translate("history_are_you_sure_delete_label")}: - strong "{{ labelDetails.comment }}" - | ? - .modal-footer - button.btn.btn-default( - type="button" - ng-disabled="state.inflight" - ng-click="$dismiss()" - ) #{translate("cancel")} - button.btn.btn-primary( - type="button" - ng-click="deleteLabel()" - ng-disabled="state.inflight" - ) {{ state.inflight ? '#{translate("history_deleting_label")}' : '#{translate("history_delete_label")}' }} + ) \ No newline at end of file diff --git a/services/web/public/coffee/ide/history/components/historyEntry.coffee b/services/web/public/coffee/ide/history/components/historyEntry.coffee index eb7f1e7266..0942613ef2 100644 --- a/services/web/public/coffee/ide/history/components/historyEntry.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntry.coffee @@ -4,6 +4,9 @@ define [ ], (App, displayNameForUser) -> historyEntryController = ($scope, $element, $attrs, _) -> ctrl = @ + # This method (and maybe the one below) will be removed soon. User details data will be + # injected into the history API responses, so we won't need to fetch user data from other + # local data structures. _getUserById = (id) -> _.find ctrl.users, (user) -> curUserId = user?._id or user?.id diff --git a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee index 90c8142c6c..cfd5bb75c5 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee @@ -8,6 +8,9 @@ define [ projectUsers = $scope.project.members.concat $scope.project.owner + # This method (and maybe the one below) will be removed soon. User details data will be + # injected into the history API responses, so we won't need to fetch user data from other + # local data structures. _getUserById = (id) -> _.find projectUsers, (user) -> curUserId = user?._id or user?.id From fa37caef58d84d645b40aa57e8d2dc1c6b2fabb5 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Mon, 6 Aug 2018 12:37:18 +0100 Subject: [PATCH 034/104] remove null check on mailchimp at top of file I didn't like the if statment being after the require, mailchimp could also be null and called elsewhere by acident --- .../app/coffee/Features/Newsletter/NewsletterManager.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee index 7baa317632..4850554303 100644 --- a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee +++ b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee @@ -3,7 +3,6 @@ logger = require 'logger-sharelatex' Settings = require 'settings-sharelatex' crypto = require('crypto') Mailchimp = require('mailchimp-api-v3') -mailchimp = new Mailchimp(Settings.mailchimp?.api_key) if Settings.mailchimp? module.exports = subscribe: (user, callback = () ->)-> @@ -12,6 +11,7 @@ module.exports = return callback() options = buildOptions(user, true) logger.log options:options, user:user, email:user.email, "trying to subscribe user to the mailing list" + mailchimp = new Mailchimp(Settings.mailchimp?.api_key) mailchimp.request options, (err)-> if err? logger.err err:err, "error subscribing person to newsletter" @@ -25,6 +25,7 @@ module.exports = return callback() logger.log user:user, email:user.email, "trying to unsubscribe user to the mailing list" options = buildOptions(user, false) + mailchimp = new Mailchimp(Settings.mailchimp?.api_key) mailchimp.request options, (err)-> if err? logger.err err:err, "error unsubscribing person to newsletter" From efcd3577ce758c7705b069931d3ba9949ad0e40e Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 6 Aug 2018 16:56:44 +0100 Subject: [PATCH 035/104] avoid clobbering imported image names --- .../Features/Project/ProjectCreationHandler.coffee | 3 ++- .../coffee/Project/ProjectCreationHandlerTests.coffee | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee b/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee index 529164a49e..f83d6e7d21 100644 --- a/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee @@ -55,7 +55,8 @@ module.exports = ProjectCreationHandler = if Settings.apis?.project_history?.displayHistoryForNewProjects project.overleaf.history.display = true if Settings.currentImageName? - project.imageName = Settings.currentImageName + # avoid clobbering any imageName already set in attributes (e.g. importedImageName) + project.imageName ?= Settings.currentImageName project.rootFolder[0] = rootFolder User.findById owner_id, "ace.spellCheckLanguage", (err, user)-> project.spellCheckLanguage = user.ace.spellCheckLanguage diff --git a/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee index 6d86545622..15499e6f71 100644 --- a/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee @@ -111,7 +111,7 @@ describe 'ProjectCreationHandler', -> project.spellCheckLanguage.should.equal "de" done() - it "should set the imageName to currentImageName if set", (done) -> + it "should set the imageName to currentImageName if set and no imageName attribute", (done) -> @Settings.currentImageName = "mock-image-name" @handler.createBlankProject ownerId, projectName, (err, project)=> project.imageName.should.equal @Settings.currentImageName @@ -123,6 +123,14 @@ describe 'ProjectCreationHandler', -> expect(project.imageName).to.not.exist done() + it "should set the imageName to the attribute value if set and not overwrite it with the currentImageName", (done) -> + @Settings.currentImageName = "mock-image-name" + attributes = + imageName: "attribute-image-name" + @handler.createBlankProject ownerId, projectName, attributes, (err, project)=> + project.imageName.should.equal attributes.imageName + done() + it "should not set the overleaf.history.display if not configured in settings", (done) -> @Settings.apis.project_history.displayHistoryForNewProjects = false @handler.createBlankProject ownerId, projectName, (err, project)=> From 6cf1f71604923bd042c505e248be845f6c724fff Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Mon, 6 Aug 2018 17:43:03 +0100 Subject: [PATCH 036/104] add newsletter checkbox to user register forms --- .../coffee/Features/User/UserRegistrationHandler.coffee | 5 +++-- services/web/public/stylesheets/app/homepage.less | 4 ++++ .../unit/coffee/User/UserRegistrationHandlerTests.coffee | 8 +++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee b/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee index df7fe93218..848434271f 100644 --- a/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee +++ b/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee @@ -1,4 +1,4 @@ -sanitize = require('sanitizer') +resanitize = require('sanitizer') User = require("../../models/User").User UserCreator = require("./UserCreator") UserGetter = require("./UserGetter") @@ -54,7 +54,8 @@ module.exports = UserRegistrationHandler = (cb)-> User.update {_id: user._id}, {"$set":{holdingAccount:false}}, cb (cb)-> AuthenticationManager.setUserPassword user._id, userDetails.password, cb (cb)-> - NewsLetterManager.subscribe user, -> + if userDetails.subscribeToNewsletter == "true" + NewsLetterManager.subscribe user, -> cb() #this can be slow, just fire it off ], (err)-> logger.log user: user, "registered" diff --git a/services/web/public/stylesheets/app/homepage.less b/services/web/public/stylesheets/app/homepage.less index d502855b72..22efbc847d 100644 --- a/services/web/public/stylesheets/app/homepage.less +++ b/services/web/public/stylesheets/app/homepage.less @@ -47,7 +47,11 @@ } .form-group { margin-left: @line-height-computed / 2; + input[type="checkbox"] { + margin-right: 5px; + } } + } .screenshot { height: 550px; diff --git a/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee b/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee index e9a6c568dd..f8bcce30ce 100644 --- a/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee +++ b/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee @@ -132,11 +132,17 @@ describe "UserRegistrationHandler", -> @AuthenticationManager.setUserPassword.calledWith(@user._id, @passingRequest.password).should.equal true done() - it "should add the user to the news letter manager", (done)-> + it "should add the user to the newsletter if accepted terms", (done)-> + @passingRequest.subscribeToNewsletter = "true" @handler.registerNewUser @passingRequest, (err)=> @NewsLetterManager.subscribe.calledWith(@user).should.equal true done() + it "should not add the user to the newsletter if not accepted terms", (done)-> + @handler.registerNewUser @passingRequest, (err)=> + @NewsLetterManager.subscribe.calledWith(@user).should.equal false + done() + it "should track the registration event", (done)-> @handler.registerNewUser @passingRequest, (err)=> @AnalyticsManager.recordEvent From c26a85c02c40df5e28b879aabae14637ed79ce9f Mon Sep 17 00:00:00 2001 From: Nate Stemen Date: Tue, 7 Aug 2018 09:49:50 -0400 Subject: [PATCH 037/104] get rid of confusing regex --- .../aceEditor/auto-complete/AutoCompleteManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e91c92f643..3be4369213 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 @@ -162,7 +162,7 @@ define [ cursorPosition = @editor.getCursorPosition() end = change.end {lineUpToCursor, commandFragment} = Helpers.getContext(@editor, end) - if lineUpToCursor.match(/.*((?![\\]).|^)%.*/) + if (i = lineUpToCursor.indexOf('%') > -1 and lineUpToCursor[i-1] != '\\') return lastCharIsBackslash = lineUpToCursor.slice(-1) == "\\" lastTwoChars = lineUpToCursor.slice(-2) From 88f2b3670bbe65f602a2b7b1c93be262a2c07b33 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 7 Aug 2018 16:48:35 +0100 Subject: [PATCH 038/104] remove unneed style --- services/web/public/stylesheets/app/homepage.less | 4 ---- 1 file changed, 4 deletions(-) diff --git a/services/web/public/stylesheets/app/homepage.less b/services/web/public/stylesheets/app/homepage.less index 22efbc847d..d502855b72 100644 --- a/services/web/public/stylesheets/app/homepage.less +++ b/services/web/public/stylesheets/app/homepage.less @@ -47,11 +47,7 @@ } .form-group { margin-left: @line-height-computed / 2; - input[type="checkbox"] { - margin-right: 5px; - } } - } .screenshot { height: 550px; From 6208e9f2d0cbf673ab1b69c0e2a48b915c2a9e4f Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 7 Aug 2018 21:38:31 +0100 Subject: [PATCH 039/104] add changeEmail function to newsletter manager not actually called --- .../Newsletter/NewsletterManager.coffee | 30 ++++++++++++++----- .../coffee/Features/User/UserUpdater.coffee | 1 + 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee index 4850554303..0c1a98a6b1 100644 --- a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee +++ b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee @@ -4,14 +4,19 @@ Settings = require 'settings-sharelatex' crypto = require('crypto') Mailchimp = require('mailchimp-api-v3') + +if !Settings.mailchimp?.api_key? + logger.warn "No newsletter provider configured so not chaning email on user" + mailchimp = + request: (opts, cb)-> cb() +else + mailchimp = new Mailchimp(Settings.mailchimp?.api_key) + module.exports = + subscribe: (user, callback = () ->)-> - if !Settings.mailchimp? - logger.warn "No newsletter provider configured so not subscribing user" - return callback() options = buildOptions(user, true) logger.log options:options, user:user, email:user.email, "trying to subscribe user to the mailing list" - mailchimp = new Mailchimp(Settings.mailchimp?.api_key) mailchimp.request options, (err)-> if err? logger.err err:err, "error subscribing person to newsletter" @@ -20,12 +25,8 @@ module.exports = callback(err) unsubscribe: (user, callback = () ->)-> - if !Settings.mailchimp? - logger.warn "No newsletter provider configured so not unsubscribing user" - return callback() logger.log user:user, email:user.email, "trying to unsubscribe user to the mailing list" options = buildOptions(user, false) - mailchimp = new Mailchimp(Settings.mailchimp?.api_key) mailchimp.request options, (err)-> if err? logger.err err:err, "error unsubscribing person to newsletter" @@ -33,6 +34,19 @@ module.exports = logger.log user:user, "finished unsubscribing user to the newsletter" callback(err) + changeEmail: (oldEmail, newEmail, callback = ()->)-> + options = buildOptions({email:oldEmail}) + delete options.body.status + options.body.email_address = newEmail + mailchimp.request options, (err)-> + # if the user has unsubscribed mailchimp will error on email address change + if err? and err?.message.indexOf("could not be validated") == -1 + logger.err err:err, "error changing email in newsletter" + return callback(err) + else + logger.log "finished changing email in the newsletter" + return callback() + hashEmail = (email)-> crypto.createHash('md5').update(email.toLowerCase()).digest("hex") diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index 8d5e3658f9..7d828366e7 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -11,6 +11,7 @@ EmailHelper = require "../Helpers/EmailHelper" Errors = require "../Errors/Errors" Settings = require "settings-sharelatex" request = require 'request' +NewsletterManager = require "../Newsletter/NewsletterManager" module.exports = UserUpdater = updateUser: (query, update, callback = (error) ->) -> From 9a27a39f55731cadac6b3244cb255e01fc86c1c0 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Wed, 8 Aug 2018 11:01:20 +0100 Subject: [PATCH 040/104] Make tooltip optional in the label component. --- services/web/app/views/project/editor/history.pug | 1 + .../public/coffee/ide/history/components/historyLabel.coffee | 3 +++ 2 files changed, 4 insertions(+) diff --git a/services/web/app/views/project/editor/history.pug b/services/web/app/views/project/editor/history.pug index 2f3f317561..bfb7df256b 100644 --- a/services/web/app/views/project/editor/history.pug +++ b/services/web/app/views/project/editor/history.pug @@ -77,6 +77,7 @@ script(type="text/ng-template", id="historyLabelTpl") tooltip-append-to-body="true" tooltip-template="'historyLabelTooltipTpl'" tooltip-placement="left" + tooltip-enable="$ctrl.showTooltip" ) i.fa.fa-tag |  {{ $ctrl.labelText }} diff --git a/services/web/public/coffee/ide/history/components/historyLabel.coffee b/services/web/public/coffee/ide/history/components/historyLabel.coffee index 2394b76f8b..03253663dd 100644 --- a/services/web/public/coffee/ide/history/components/historyLabel.coffee +++ b/services/web/public/coffee/ide/history/components/historyLabel.coffee @@ -3,6 +3,8 @@ define [ ], (App) -> historyLabelController = ($scope, $element, $attrs, $filter, _) -> ctrl = @ + ctrl.$onInit = () -> + ctrl.showTooltip ?= true return App.component "historyLabel", { @@ -12,6 +14,7 @@ define [ labelCreationDateTime: "<" isOwnedByCurrentUser: "<" onLabelDelete: "&" + showTooltip: " Date: Wed, 8 Aug 2018 11:01:51 +0100 Subject: [PATCH 041/104] Create history labels list component. --- .../project/editor/history/entriesListV2.pug | 18 +++++++++++- .../components/historyLabelsList.coffee | 29 +++++++++++++++++++ .../stylesheets/app/editor/history-v2.less | 8 +++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 services/web/public/coffee/ide/history/components/historyLabelsList.coffee diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index e08de0effd..fa1ece2fdf 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -3,6 +3,7 @@ aside.change-list( ng-controller="HistoryV2ListController" ) history-entries-list( + ng-if="!history.showOnlyLabels" entries="history.updates" current-user="user" users="projectUsers" @@ -13,6 +14,10 @@ aside.change-list( on-entry-select="handleEntrySelect(selectedEntry)" on-label-delete="handleLabelDelete(label)" ) + history-labels-list( + ng-if="history.showOnlyLabels" + labels="history.labels" + ) aside.change-list( ng-if="history.isV2 && history.viewMode === HistoryViewModes.COMPARE" @@ -193,4 +198,15 @@ script(type="text/ng-template", id="historyEntryTpl") li.history-entry-metadata-user(ng-if="::$ctrl.entry.meta.users.length == 0") span.name( ng-style="$ctrl.getUserCSSStyle();" - ) #{translate("anonymous")} \ No newline at end of file + ) #{translate("anonymous")} + +script(type="text/ng-template", id="historyLabelsListTpl") + history-label( + ng-repeat="label in $ctrl.labels" + show-tooltip="false" + label-text="label.comment" + label-owner-name="$ctrl.displayNameById(label.user_id)" + label-creation-date-time="label.created_at" + is-owned-by-current-user="label.user_id === $ctrl.currentUser.id" + on-label-delete="$ctrl.onLabelDelete({ label: label })" + ) \ No newline at end of file diff --git a/services/web/public/coffee/ide/history/components/historyLabelsList.coffee b/services/web/public/coffee/ide/history/components/historyLabelsList.coffee new file mode 100644 index 0000000000..b692233a7e --- /dev/null +++ b/services/web/public/coffee/ide/history/components/historyLabelsList.coffee @@ -0,0 +1,29 @@ +define [ + "base" + "ide/history/util/displayNameForUser" +], (App, displayNameForUser) -> + historyLabelsListController = ($scope, $element, $attrs) -> + ctrl = @ + # This method (and maybe the one below) will be removed soon. User details data will be + # injected into the history API responses, so we won't need to fetch user data from other + # local data structures. + _getUserById = (id) -> + _.find ctrl.users, (user) -> + curUserId = user?._id or user?.id + curUserId == id + ctrl.displayName = displayNameForUser + ctrl.displayNameById = (id) -> + displayNameForUser(_getUserById(id)) + return + + App.component "historyLabelsList", { + bindings: + labels: "<" + users: "<" + isLoading: "<" + currentUser: "<" + onLabelSelect: "&" + onLabelDelete: "&" + controller: historyLabelsListController + templateUrl: "historyLabelsListTpl" + } diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index 404609917a..abbcef147d 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -27,6 +27,14 @@ margin-left: (@line-height-computed / 2); } + .history-toolbar-entries-list { + position: absolute; + right: 0; + width: @changesListWidth; + padding: 0 10px; + border-left: 1px solid @editor-border-color; + } + .history-entries { font-size: @history-base-font-size; color: @history-base-color; From cd853abb3c039a9ee0fa912e34502fb90ea30c43 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Wed, 8 Aug 2018 11:02:14 +0100 Subject: [PATCH 042/104] Load labels list from the backend. --- .../views/project/editor/history/toolbarV2.pug | 8 ++++++++ .../coffee/ide/history/HistoryV2Manager.coffee | 17 ++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index 2a4c222bad..8774754de2 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -20,6 +20,14 @@ i.fa.fa-exchange |  #{translate("compare_to_another_version")} + .history-toolbar-entries-list + toggle-switch( + ng-model="history.showOnlyLabels" + label-true="Labels" + label-false="All history" + description="Show all of the project history or only labelled versions." + ) + script(type="text/ng-template", id="historyV2AddLabelModalTemplate") form( name="addLabelModalForm" diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index 7ff41985f4..519a94a54a 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -12,6 +12,7 @@ define [ "ide/history/directives/infiniteScroll" "ide/history/components/historyEntriesList" "ide/history/components/historyEntry" + "ide/history/components/historyLabelsList" "ide/history/components/historyLabel" "ide/history/components/historyFileTree" "ide/history/components/historyFileEntity" @@ -72,6 +73,7 @@ define [ toV: null } } + showOnlyLabels: false labels: null files: [] diff: null # When history.viewMode == HistoryViewModes.COMPARE @@ -134,17 +136,26 @@ define [ updatesURL = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}" if @$scope.history.nextBeforeTimestamp? updatesURL += "&before=#{@$scope.history.nextBeforeTimestamp}" - + labelsURL = "/project/#{@ide.project_id}/labels" + @$scope.history.loading = true @$scope.history.loadingFileTree = true - @ide.$http.get updatesURL + requests = + updates: @ide.$http.get updatesURL + + if !@$scope.history.labels? + requests.labels = @ide.$http.get labelsURL + + @ide.$q.all requests .then (response) => - updatesData = response.data + updatesData = response.updates.data @_loadUpdates(updatesData.updates) @$scope.history.nextBeforeTimestamp = updatesData.nextBeforeTimestamp if !updatesData.nextBeforeTimestamp? @$scope.history.atEnd = true + if response.labels? + @$scope.history.labels = response.labels.data @$scope.history.loading = false loadFileAtPointInTime: () -> From f2b957e5b33fed3931f3197c87208e88de06bc2d Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Wed, 8 Aug 2018 13:46:43 +0100 Subject: [PATCH 043/104] Add history labels view. --- .../web/app/views/project/editor/history.pug | 8 +- .../project/editor/history/entriesListV2.pug | 44 ++- .../history/components/historyEntry.coffee | 6 +- .../history/components/historyLabel.coffee | 4 +- .../components/historyLabelsList.coffee | 16 +- .../HistoryV2ListController.coffee | 3 + .../stylesheets/app/editor/history-v2.less | 349 +----------------- .../stylesheets/app/editor/toolbar.less | 57 +-- 8 files changed, 103 insertions(+), 384 deletions(-) diff --git a/services/web/app/views/project/editor/history.pug b/services/web/app/views/project/editor/history.pug index bfb7df256b..4782a5fa3d 100644 --- a/services/web/app/views/project/editor/history.pug +++ b/services/web/app/views/project/editor/history.pug @@ -70,10 +70,10 @@ script(type="text/ng-template", id="historyRestoreDiffModalTemplate") script(type="text/ng-template", id="historyLabelTpl") - .history-entry-label( - ng-class="{ 'history-entry-label-own' : $ctrl.isOwnedByCurrentUser }" + .history-label( + ng-class="{ 'history-label-own' : $ctrl.isOwnedByCurrentUser }" ) - span.history-entry-label-comment( + span.history-label-comment( tooltip-append-to-body="true" tooltip-template="'historyLabelTooltipTpl'" tooltip-placement="left" @@ -81,7 +81,7 @@ script(type="text/ng-template", id="historyLabelTpl") ) i.fa.fa-tag |  {{ $ctrl.labelText }} - button.history-entry-label-delete-btn( + button.history-label-delete-btn( ng-if="$ctrl.isOwnedByCurrentUser" stop-propagation="click" ng-click="$ctrl.onLabelDelete()" diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index fa1ece2fdf..e8bebabe69 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -17,6 +17,11 @@ aside.change-list( history-labels-list( ng-if="history.showOnlyLabels" labels="history.labels" + current-user="user" + users="projectUsers" + is-loading="history.loading" + on-label-select="handleLabelSelect(label)" + on-label-delete="handleLabelDelete(label)" ) aside.change-list( @@ -201,12 +206,33 @@ script(type="text/ng-template", id="historyEntryTpl") ) #{translate("anonymous")} script(type="text/ng-template", id="historyLabelsListTpl") - history-label( - ng-repeat="label in $ctrl.labels" - show-tooltip="false" - label-text="label.comment" - label-owner-name="$ctrl.displayNameById(label.user_id)" - label-creation-date-time="label.created_at" - is-owned-by-current-user="label.user_id === $ctrl.currentUser.id" - on-label-delete="$ctrl.onLabelDelete({ label: label })" - ) \ No newline at end of file + .history-labels-list + .history-entry-label( + ng-repeat="label in $ctrl.labels | orderBy : [ '-version', '-created_at' ] track by label.id" + ng-click="$ctrl.onLabelSelect({ label: label })" + ) + history-label( + show-tooltip="false" + label-text="label.comment" + is-owned-by-current-user="label.user_id === $ctrl.currentUser.id" + on-label-delete="$ctrl.onLabelDelete({ label: label })" + ) + .history-entry-label-metadata + .history-entry-label-metadata-user(ng-init="user = $ctrl.getUserById(label.user_id)") + | Saved by + span.name( + ng-if="user && user._id !== $ctrl.currentUser.id" + ng-style="$ctrl.getUserCSSStyle(user);" + ) {{ ::$ctrl.displayName(user) }} + span.name( + ng-if="user && user._id == $ctrl.currentUser.id" + ng-style="$ctrl.getUserCSSStyle(user);" + ) You + span.name( + ng-if="user == null" + ng-style="$ctrl.getUserCSSStyle(user);" + ) #{translate("anonymous")} + time.history-entry-label-metadata-time {{ ::label.created_at | formatDate }} + .loading(ng-show="$ctrl.isLoading") + i.fa.fa-spin.fa-refresh + |    #{translate("loading")}... \ No newline at end of file diff --git a/services/web/public/coffee/ide/history/components/historyEntry.coffee b/services/web/public/coffee/ide/history/components/historyEntry.coffee index 0942613ef2..8af726b509 100644 --- a/services/web/public/coffee/ide/history/components/historyEntry.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntry.coffee @@ -1,7 +1,8 @@ define [ "base" + "ide/colors/ColorManager" "ide/history/util/displayNameForUser" -], (App, displayNameForUser) -> +], (App, ColorManager, displayNameForUser) -> historyEntryController = ($scope, $element, $attrs, _) -> ctrl = @ # This method (and maybe the one below) will be removed soon. User details data will be @@ -19,7 +20,8 @@ define [ else if projectOp.add? then "#{ projectOp.add.pathname}" else if projectOp.remove? then "#{ projectOp.remove.pathname}" ctrl.getUserCSSStyle = (user) -> - hue = user?.hue or 100 + curUserId = user?._id or user?.id + hue = ColorManager.getHueForUserId(curUserId) or 100 if ctrl.entry.inSelection color : "#FFF" else diff --git a/services/web/public/coffee/ide/history/components/historyLabel.coffee b/services/web/public/coffee/ide/history/components/historyLabel.coffee index 03253663dd..680d610d99 100644 --- a/services/web/public/coffee/ide/history/components/historyLabel.coffee +++ b/services/web/public/coffee/ide/history/components/historyLabel.coffee @@ -10,8 +10,8 @@ define [ App.component "historyLabel", { bindings: labelText: "<" - labelOwnerName: "<" - labelCreationDateTime: "<" + labelOwnerName: " +], (App, ColorManager, displayNameForUser) -> historyLabelsListController = ($scope, $element, $attrs) -> ctrl = @ # This method (and maybe the one below) will be removed soon. User details data will be # injected into the history API responses, so we won't need to fetch user data from other # local data structures. - _getUserById = (id) -> + ctrl.getUserById = (id) -> _.find ctrl.users, (user) -> curUserId = user?._id or user?.id curUserId == id ctrl.displayName = displayNameForUser - ctrl.displayNameById = (id) -> - displayNameForUser(_getUserById(id)) + ctrl.getUserCSSStyle = (user) -> + curUserId = user?._id or user?.id + hue = ColorManager.getHueForUserId(curUserId) or 100 + if false #ctrl.entry.inSelection + color : "#FFF" + else + color: "hsl(#{ hue }, 70%, 50%)" return App.component "historyLabelsList", { bindings: labels: "<" users: "<" - isLoading: "<" currentUser: "<" + isLoading: "<" onLabelSelect: "&" onLabelDelete: "&" controller: historyLabelsListController diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee index f7c602733f..a2873d47d8 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee @@ -16,6 +16,9 @@ define [ ide.historyManager.selectUpdate(entry) $scope.recalculateSelectedUpdates() + $scope.handleLabelSelect = (label) -> + console.log label + $scope.handleLabelDelete = (labelDetails) -> $modal.open( templateUrl: "historyV2DeleteLabelModalTemplate" diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index abbcef147d..c2e7ce6b26 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -61,8 +61,7 @@ color: #FFF; } } - - .history-entry-label { + .history-label { display: inline-block; color: @history-entry-label-color; font-size: @font-size-small; @@ -73,8 +72,8 @@ color: @history-entry-selected-label-color; } } - .history-entry-label-comment, - .history-entry-label-delete-btn { + .history-label-comment, + .history-label-delete-btn { padding: 0 @padding-xs-horizontal 1px @padding-xs-horizontal; border: 0; background-color: @history-entry-label-bg-color; @@ -82,19 +81,19 @@ background-color: @history-entry-selected-label-bg-color; } } - .history-entry-label-comment { + .history-label-comment { display: block; float: left; border-radius: 9999px; max-width: 190px; overflow: hidden; text-overflow: ellipsis; - .history-entry-label-own & { + .history-label-own & { padding-right: (@padding-xs-horizontal / 2); border-radius: 9999px 0 0 9999px; } } - .history-entry-label-delete-btn { + .history-label-delete-btn { padding-left: (@padding-xs-horizontal / 2); padding-right: @padding-xs-horizontal; border-radius: 0 9999px 9999px 0; @@ -163,6 +162,15 @@ } } +.history-labels-list { + .history-entries; + overflow-y: auto; +} + .history-entry-label { + .history-entry-details; + padding: 7px 10px; + } + .history-file-tree-inner { .full-size; overflow-y: auto; @@ -239,330 +247,3 @@ color: @brand-primary; } } -// @changesListWidth: 250px; -// @changesListPadding: @line-height-computed / 2; - -// @selector-padding-vertical: 10px; -// @selector-padding-horizontal: @line-height-computed / 2; -// @day-header-height: 24px; - -// @range-bar-color: @link-color; -// @range-bar-selected-offset: 14px; - -// #history { -// .upgrade-prompt { -// position: absolute; -// top: 0; -// bottom: 0; -// left: 0; -// right: 0; -// z-index: 100; -// background-color: rgba(128,128,128,0.4); -// .message { -// margin: auto; -// margin-top: 100px; -// padding: (@line-height-computed / 2) @line-height-computed; -// width: 400px; -// background-color: white; -// border-radius: 8px; -// } -// .message-wider { -// width: 650px; -// margin-top: 60px; -// padding: 0; -// } - -// .message-header { -// .modal-header; -// } - -// .message-body { -// .modal-body; -// } -// } - -// .diff-panel { -// .full-size; -// margin-right: @changesListWidth; -// } - -// .diff { -// .full-size; -// .toolbar { -// padding: 3px; -// .name { -// float: left; -// padding: 3px @line-height-computed / 4; -// display: inline-block; -// } -// } -// .diff-editor { -// .full-size; -// top: 40px; -// } -// .hide-ace-cursor { -// .ace_active-line, .ace_cursor-layer, .ace_gutter-active-line { -// display: none; -// } -// } -// .diff-deleted { -// padding: @line-height-computed; -// } -// .deleted-warning { -// background-color: @brand-danger; -// color: white; -// padding: @line-height-computed / 2; -// margin-right: @line-height-computed / 4; -// } -// &-binary { -// .alert { -// margin: @line-height-computed / 2; -// } -// } -// } - -// aside.change-list { -// border-left: 1px solid @editor-border-color; -// height: 100%; -// width: @changesListWidth; -// position: absolute; -// right: 0; - -// .loading { -// text-align: center; -// font-family: @font-family-serif; -// } - -// ul { -// li.change { -// position: relative; -// user-select: none; -// -ms-user-select: none; -// -moz-user-select: none; -// -webkit-user-select: none; - -// .day { -// background-color: #fafafa; -// border-bottom: 1px solid @editor-border-color; -// padding: 4px; -// font-weight: bold; -// text-align: center; -// height: @day-header-height; -// font-size: 14px; -// line-height: 1; -// } -// .selectors { -// input { -// margin: 0; -// } -// position: absolute; -// left: @selector-padding-horizontal; -// top: 0; -// bottom: 0; -// width: 24px; -// .selector-from { -// position: absolute; -// bottom: @selector-padding-vertical; -// left: 0; -// opacity: 0.8; -// } -// .selector-to { -// position: absolute; -// top: @selector-padding-vertical; -// left: 0; -// opacity: 0.8; -// } -// .range { -// position: absolute; -// left: 5px; -// width: 4px; -// top: 0; -// bottom: 0; -// } -// } -// .description { -// padding: (@line-height-computed / 4); -// padding-left: 38px; -// min-height: 38px; -// border-bottom: 1px solid @editor-border-color; -// cursor: pointer; -// &:hover { -// background-color: @gray-lightest; -// } -// } -// .users { -// .user { -// font-size: 0.8rem; -// color: @gray; -// text-transform: capitalize; -// position: relative; -// padding-left: 16px; -// .color-square { -// height: 12px; -// width: 12px; -// border-radius: 3px; -// position: absolute; -// left: 0; -// bottom: 3px; -// } -// .name { -// width: 94%; -// white-space: nowrap; -// overflow: hidden; -// text-overflow: ellipsis; -// } -// } -// } -// .time { -// float: right; -// color: @gray; -// display: inline-block; -// padding-right: (@line-height-computed / 2); -// font-size: 0.8rem; -// line-height: @line-height-computed; -// } -// .doc { -// font-size: 0.9rem; -// font-weight: bold; -// } -// .action { -// color: @gray; -// text-transform: uppercase; -// font-size: 0.7em; -// margin-bottom: -2px; -// margin-top: 2px; -// &-edited { -// margin-top: 0; -// } -// } -// } -// li.loading-changes, li.empty-message { -// padding: 6px; -// cursor: default; -// &:hover { -// background-color: inherit; -// } -// } -// li.selected { -// border-left: 4px solid @range-bar-color; -// .day { -// padding-left: 0; -// } -// .description { -// padding-left: 34px; -// } -// .selectors { -// left: @selector-padding-horizontal - 4px; -// .range { -// background-color: @range-bar-color; -// } -// } -// } -// li.selected-to { -// .selectors { -// .range { -// top: @range-bar-selected-offset; -// } -// .selector-to { -// opacity: 1; -// } -// } -// } -// li.selected-from { -// .selectors { -// .range { -// bottom: @range-bar-selected-offset; -// } -// .selector-from { -// opacity: 1; -// } -// } -// } -// li.first-in-day { -// .selectors { -// .selector-to { -// top: @day-header-height + @selector-padding-vertical; -// } -// } -// } -// li.first-in-day.selected-to { -// .selectors { -// .range { -// top: @day-header-height + @range-bar-selected-offset; -// } -// } -// } -// } -// ul.hover-state { -// li { -// .selectors { -// .range { -// background-color: transparent; -// top: 0; -// bottom: 0; -// } -// } -// } -// li.hover-selected { -// .selectors { -// .range { -// top: 0; -// background-color: @gray-light; -// } -// } -// } -// li.hover-selected-to { -// .selectors { -// .range { -// top: @range-bar-selected-offset; -// } -// .selector-to { -// opacity: 1; -// } -// } -// } -// li.hover-selected-from { -// .selectors { -// .range { -// bottom: @range-bar-selected-offset; -// } -// .selector-from { -// opacity: 1; -// } -// } -// } -// li.first-in-day.hover-selected-to { -// .selectors { -// .range { -// top: @day-header-height + @range-bar-selected-offset; -// } -// } -// } -// } -// } -// } - -// .diff-deleted { -// padding-top: 15px; -// } - -// .editor-dark { -// #history { -// aside.change-list { -// border-color: @editor-dark-toolbar-border-color; - -// ul li.change { -// .day { -// background-color: darken(@editor-dark-background-color, 10%); -// border-bottom: 1px solid @editor-dark-toolbar-border-color; -// } -// .description { -// border-bottom: 1px solid @editor-dark-toolbar-border-color; -// &:hover { -// background-color: black; -// } -// } -// } -// } -// } -// } diff --git a/services/web/public/stylesheets/app/editor/toolbar.less b/services/web/public/stylesheets/app/editor/toolbar.less index 4006b56a7b..c5499180e0 100644 --- a/services/web/public/stylesheets/app/editor/toolbar.less +++ b/services/web/public/stylesheets/app/editor/toolbar.less @@ -194,56 +194,57 @@ } .toggle-switch { - position: relative; + position: relative; height: 100%; width: 100%; - background-color: @toggle-switch-bg; - border-radius: @btn-border-radius-base; + background-color: @toggle-switch-bg; + border-radius: @btn-border-radius-base; } .toggle-switch-label { position: relative; display: block; font-weight: normal; - z-index: 2; - float: left; - width: 50%; - height: 100%; - line-height: 24px; + z-index: 2; + float: left; + width: 50%; + height: 100%; + line-height: 24px; text-align: center; margin-bottom: 0; - cursor: pointer; - user-select: none; - transition: color 0.12s ease-out; + cursor: pointer; + user-select: none; + color: @text-color; + transition: color 0.12s ease-out; } .toggle-switch-input { - position: absolute; - opacity: 0; + position: absolute; + opacity: 0; } .toggle-switch-input:checked + .toggle-switch-label { - color: #fff; - font-weight: bold; + color: #fff; + font-weight: bold; } .toggle-switch-selection { - display: block; - position: absolute; - z-index: 1; - top: 2px; - left: 2px; - right: 2px; - width: calc(~"50% - 2px"); - height: calc(~"100% - 4px"); - background: @toggle-switch-highlight-color; - border-radius: @btn-border-radius-base 0 0 @btn-border-radius-base; - transition: transform 0.12s ease-out, border-radius 0.12s ease-out; + display: block; + position: absolute; + z-index: 1; + top: 2px; + left: 2px; + right: 2px; + width: calc(~"50% - 2px"); + height: calc(~"100% - 4px"); + background: @toggle-switch-highlight-color; + border-radius: @btn-border-radius-base 0 0 @btn-border-radius-base; + transition: transform 0.12s ease-out, border-radius 0.12s ease-out; } .toggle-switch-input:checked:nth-child(4) ~ .toggle-switch-selection { - transform: translate(100%); - border-radius: 0 @btn-border-radius-base @btn-border-radius-base 0; + transform: translate(100%); + border-radius: 0 @btn-border-radius-base @btn-border-radius-base 0; } /************************************** From 687637eec71bacd2aa8b8a92ced6330aa2d9c728 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 8 Aug 2018 13:15:51 +0100 Subject: [PATCH 044/104] change email address in newsletter when changing default email put mongo_id into mailchimp merge fields --- .../Newsletter/NewsletterManager.coffee | 2 +- .../coffee/Features/User/UserUpdater.coffee | 22 ++++++++++------ .../unit/coffee/User/UserUpdaterTests.coffee | 26 ++++++++++++++++--- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee index 0c1a98a6b1..431eb214c3 100644 --- a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee +++ b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee @@ -4,7 +4,6 @@ Settings = require 'settings-sharelatex' crypto = require('crypto') Mailchimp = require('mailchimp-api-v3') - if !Settings.mailchimp?.api_key? logger.warn "No newsletter provider configured so not chaning email on user" mailchimp = @@ -63,5 +62,6 @@ buildOptions = (user, is_subscribed)-> merge_fields: FNAME: user.first_name LNAME: user.last_name + MONGO_ID:user._id return opts diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index 7d828366e7..5770c7b834 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -100,15 +100,21 @@ module.exports = UserUpdater = setDefaultEmailAddress: (userId, email, callback) -> email = EmailHelper.parseEmail(email) return callback(new Error('invalid email')) if !email? - query = _id: userId, 'emails.email': email - update = $set: email: email - @updateUser query, update, (error, res) -> - if error? - logger.err error:error, 'problem setting default emails' + UserGetter.getUserEmail userId, (error, oldEmail) => + if err? return callback(error) - if res.n == 0 # TODO: Check n or nMatched? - return callback(new Error('Default email does not belong to user')) - callback() + query = _id: userId, 'emails.email': email + update = $set: email: email + @updateUser query, update, (error, res) -> + if error? + logger.err error:error, 'problem setting default emails' + return callback(error) + else if res.n == 0 # TODO: Check n or nMatched? + return callback(new Error('Default email does not belong to user')) + else + NewsletterManager.changeEmail oldEmail, email, callback + + updateV1AndSetDefaultEmailAddress: (userId, email, callback) -> @updateEmailAddressInV1 userId, email, (error) => diff --git a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee index f2ac951727..17f691edba 100644 --- a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee +++ b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee @@ -18,21 +18,27 @@ describe "UserUpdater", -> getUserEmail: sinon.stub() getUserByAnyEmail: sinon.stub() ensureUniqueEmailAddress: sinon.stub() - @logger = err: sinon.stub(), log: -> + @logger = + err: sinon.stub() + log: -> + warn: -> @addAffiliation = sinon.stub().yields() @removeAffiliation = sinon.stub().callsArgWith(2, null) @refreshFeatures = sinon.stub().yields() + @NewsletterManager = + changeEmail:sinon.stub() @UserUpdater = SandboxedModule.require modulePath, requires: "logger-sharelatex": @logger + "../../infrastructure/mongojs":@mongojs + "metrics-sharelatex": timeAsyncMethod: sinon.stub() "./UserGetter": @UserGetter '../Institutions/InstitutionsAPI': addAffiliation: @addAffiliation removeAffiliation: @removeAffiliation '../Subscription/FeaturesUpdater': refreshFeatures: @refreshFeatures - "../../infrastructure/mongojs":@mongojs - "metrics-sharelatex": timeAsyncMethod: sinon.stub() "settings-sharelatex": @settings = {} "request": @request = {} + "../Newsletter/NewsletterManager": @NewsletterManager @stubbedUser = _id: "3131231" @@ -174,6 +180,10 @@ describe "UserUpdater", -> done() describe 'setDefaultEmailAddress', -> + beforeEach -> + @UserGetter.getUserEmail.callsArgWith(1, null, @stubbedUser.email) + @NewsletterManager.changeEmail.callsArgWith(2, null) + it 'set default', (done)-> @UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, n: 1) @@ -185,6 +195,16 @@ describe "UserUpdater", -> ).should.equal true done() + it 'set changed the email in newsletter', (done)-> + @UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, n: 1) + + @UserUpdater.setDefaultEmailAddress @stubbedUser._id, @newEmail, (err)=> + should.not.exist(err) + @NewsletterManager.changeEmail.calledWith( + @stubbedUser.email, @newEmail + ).should.equal true + done() + it 'handle error', (done)-> @UserUpdater.updateUser = sinon.stub().callsArgWith(2, new Error('nope')) From c68366155e0e2e56c47ba9c0c9492b4aa0d440f3 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 8 Aug 2018 14:14:56 +0100 Subject: [PATCH 045/104] remove sanitize = require('sanitizer') not used anywhere --- .../web/app/coffee/Features/User/UserRegistrationHandler.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee b/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee index 848434271f..1291142dab 100644 --- a/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee +++ b/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee @@ -1,4 +1,3 @@ -resanitize = require('sanitizer') User = require("../../models/User").User UserCreator = require("./UserCreator") UserGetter = require("./UserGetter") From df161d3ece9f11673e7cdc04fd6ea6ec36e02528 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 8 Aug 2018 14:32:36 +0100 Subject: [PATCH 046/104] change newsletter log to info on process boot --- .../app/coffee/Features/Newsletter/NewsletterManager.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee index 431eb214c3..5fb2e00eb7 100644 --- a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee +++ b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee @@ -5,10 +5,11 @@ crypto = require('crypto') Mailchimp = require('mailchimp-api-v3') if !Settings.mailchimp?.api_key? - logger.warn "No newsletter provider configured so not chaning email on user" + logger.info "Using newsletter provider: none" mailchimp = request: (opts, cb)-> cb() else + logger.info "Using newsletter provider: mailchimp" mailchimp = new Mailchimp(Settings.mailchimp?.api_key) module.exports = From d59fe61a83feb9775066012378968e4df14199f7 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 9 Aug 2018 11:03:00 +0100 Subject: [PATCH 047/104] Handle sorting of labels; keep selection between view changes. --- .../project/editor/history/entriesListV2.pug | 11 +-- .../project/editor/history/toolbarV2.pug | 1 + .../ide/history/HistoryV2Manager.coffee | 69 +++++++++++++++---- .../components/historyLabelsList.coffee | 4 +- ...HistoryV2DeleteLabelModalController.coffee | 2 +- .../HistoryV2ListController.coffee | 5 +- .../web/public/coffee/ide/services/ide.coffee | 3 +- .../stylesheets/app/editor/history-v2.less | 19 +++-- 8 files changed, 85 insertions(+), 29 deletions(-) diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index e8bebabe69..3f936a8d11 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -152,7 +152,7 @@ script(type="text/ng-template", id="historyEntryTpl") .history-entry-details(ng-click="$ctrl.onSelect({ selectedEntry: $ctrl.entry })") history-label( - ng-repeat="label in $ctrl.entry.labels" + ng-repeat="label in $ctrl.entry.labels | orderBy : '-created_at'" label-text="label.comment" label-owner-name="$ctrl.displayNameById(label.user_id)" label-creation-date-time="label.created_at" @@ -208,8 +208,9 @@ script(type="text/ng-template", id="historyEntryTpl") script(type="text/ng-template", id="historyLabelsListTpl") .history-labels-list .history-entry-label( - ng-repeat="label in $ctrl.labels | orderBy : [ '-version', '-created_at' ] track by label.id" + ng-repeat="label in $ctrl.labels track by label.id" ng-click="$ctrl.onLabelSelect({ label: label })" + ng-class="{ 'history-entry-label-selected': label.selected }" ) history-label( show-tooltip="false" @@ -222,15 +223,15 @@ script(type="text/ng-template", id="historyLabelsListTpl") | Saved by span.name( ng-if="user && user._id !== $ctrl.currentUser.id" - ng-style="$ctrl.getUserCSSStyle(user);" + ng-style="$ctrl.getUserCSSStyle(user, label);" ) {{ ::$ctrl.displayName(user) }} span.name( ng-if="user && user._id == $ctrl.currentUser.id" - ng-style="$ctrl.getUserCSSStyle(user);" + ng-style="$ctrl.getUserCSSStyle(user, label);" ) You span.name( ng-if="user == null" - ng-style="$ctrl.getUserCSSStyle(user);" + ng-style="$ctrl.getUserCSSStyle(user, label);" ) #{translate("anonymous")} time.history-entry-label-metadata-time {{ ::label.created_at | formatDate }} .loading(ng-show="$ctrl.isLoading") diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index 8774754de2..6abe167f42 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -9,6 +9,7 @@ time.history-toolbar-time {{ history.selection.updates[0].meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }} button.history-toolbar-btn( ng-click="showAddLabelDialog();" + ng-if="!history.showOnlyLabels" ng-disabled="history.loadingFileTree" ) i.fa.fa-tag diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index 519a94a54a..2e56d955ce 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -49,6 +49,10 @@ define [ else @reloadDiff() + @$scope.$watch "history.showOnlyLabels", (showOnlyLabels, prevVal) => + if showOnlyLabels? and showOnlyLabels != prevVal and showOnlyLabels + @selectedLabelFromUpdatesSelection() + show: () -> @$scope.ui.view = "history" @reset() @@ -131,6 +135,36 @@ define [ @$scope.history.updates[selectedUpdateIndex].selectedFrom = true @loadFileTreeForUpdate @$scope.history.updates[selectedUpdateIndex] + selectLastLabel = () -> + return if @$scope.history.labels.length == 0 + # TODO Select last label + + selectedLabelFromUpdatesSelection: () -> + nLabels = @$scope.history.selection.updates?[0]?.labels?.length + if nLabels == 1 + @selectLabel @$scope.history.selection.updates[0].labels[0] + else if nLabels > 1 + sortedLabels = @ide.$filter("orderBy")(@$scope.history.selection.updates[0].labels, '-created_at') + lastLabelFromUpdate = sortedLabels[0] + @selectLabel lastLabelFromUpdate + + selectLabel: (labelToSelect) -> + labelToSelectIndex = -1 + for update, i in @$scope.history.updates + if update.toV == labelToSelect.version + labelToSelectIndex = i + break + if labelToSelectIndex == -1 + labelToSelectIndex = 0 + for update in @$scope.history.updates + update.selectedTo = false + update.selectedFrom = false + for label in @$scope.history.labels + label.selected = (labelToSelect.id == label.id) + @$scope.history.updates[labelToSelectIndex].selectedTo = true + @$scope.history.updates[labelToSelectIndex].selectedFrom = true + @loadFileTreeForUpdate @$scope.history.updates[labelToSelectIndex] + BATCH_SIZE: 10 fetchNextBatchOfUpdates: () -> updatesURL = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}" @@ -155,9 +189,12 @@ define [ if !updatesData.nextBeforeTimestamp? @$scope.history.atEnd = true if response.labels? - @$scope.history.labels = response.labels.data + @$scope.history.labels = @_sortLabelsByVersionAndDate response.labels.data @$scope.history.loading = false + _sortLabelsByVersionAndDate: (labels) -> + @ide.$filter("orderBy")(labels, [ '-version', '-created_at' ]) + loadFileAtPointInTime: () -> pathname = @$scope.history.selection.pathname toV = @$scope.history.selection.updates[0].toV @@ -219,8 +256,8 @@ define [ labelCurrentVersion: (labelComment) => @_labelVersion labelComment, @$scope.history.selection.updates[0].toV - deleteLabel: (labelId) => - url = "/project/#{@$scope.project_id}/labels/#{labelId}" + deleteLabel: (label) => + url = "/project/#{@$scope.project_id}/labels/#{label.id}" @ide.$http({ url, @@ -228,14 +265,16 @@ define [ headers: "X-CSRF-Token": window.csrfToken }).then (response) => - @_deleteLabelFromLocalCollection @$scope.history.updates, labelId - @_deleteLabelFromLocalCollection @$scope.history.selection, labelId + @_deleteLabelLocally label - - _deleteLabelFromLocalCollection: (collection, labelId) -> - for update in collection - update.labels = _.filter update.labels, (label) -> - label.id != labelId + _deleteLabelLocally: (labelToDelete) -> + for update, i in @$scope.history.updates + if update.toV == labelToDelete.version + update.labels = _.filter update.labels, (label) -> + label.id != labelToDelete.id + break + @$scope.history.labels = _.filter @$scope.history.labels, (label) -> + label.id != labelToDelete.id _parseDiff: (diff) -> if diff.binary @@ -314,7 +353,10 @@ define [ if @$scope.history.viewMode == HistoryViewModes.COMPARE @autoSelectRecentUpdates() else - @autoSelectLastUpdate() + if @$scope.history.showOnlyLabels + @selectLastLabel() + else + @autoSelectLastUpdate() _labelVersion: (comment, version) -> url = "/project/#{@$scope.project_id}/labels" @@ -327,10 +369,11 @@ define [ .then (response) => @_addLabelToLocalUpdate response.data - _addLabelToLocalUpdate: (label) -> + _addLabelToLocalUpdate: (label) => localUpdate = _.find @$scope.history.updates, (update) -> update.toV == label.version if localUpdate? - localUpdate.labels.push label + localUpdate.labels = @_sortLabelsByVersionAndDate localUpdate.labels.concat label + @$scope.history.labels = @_sortLabelsByVersionAndDate @$scope.history.labels.concat label _perDocSummaryOfUpdates: (updates) -> # Track current_pathname -> original_pathname diff --git a/services/web/public/coffee/ide/history/components/historyLabelsList.coffee b/services/web/public/coffee/ide/history/components/historyLabelsList.coffee index b95700250f..44c9a54742 100644 --- a/services/web/public/coffee/ide/history/components/historyLabelsList.coffee +++ b/services/web/public/coffee/ide/history/components/historyLabelsList.coffee @@ -13,10 +13,10 @@ define [ curUserId = user?._id or user?.id curUserId == id ctrl.displayName = displayNameForUser - ctrl.getUserCSSStyle = (user) -> + ctrl.getUserCSSStyle = (user, label) -> curUserId = user?._id or user?.id hue = ColorManager.getHueForUserId(curUserId) or 100 - if false #ctrl.entry.inSelection + if label.selected color : "#FFF" else color: "hsl(#{ hue }, 70%, 50%)" diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2DeleteLabelModalController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2DeleteLabelModalController.coffee index d03786c2c6..6001c33762 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryV2DeleteLabelModalController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2DeleteLabelModalController.coffee @@ -9,7 +9,7 @@ define [ $scope.deleteLabel = () -> $scope.state.inflight = true - ide.historyManager.deleteLabel labelDetails.id + ide.historyManager.deleteLabel labelDetails .then (response) -> $scope.state.inflight = false $modalInstance.close() diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee index a2873d47d8..7129e0374c 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee @@ -17,8 +17,9 @@ define [ $scope.recalculateSelectedUpdates() $scope.handleLabelSelect = (label) -> - console.log label - + ide.historyManager.selectLabel(label) + $scope.recalculateSelectedUpdates() + $scope.handleLabelDelete = (labelDetails) -> $modal.open( templateUrl: "historyV2DeleteLabelModalTemplate" diff --git a/services/web/public/coffee/ide/services/ide.coffee b/services/web/public/coffee/ide/services/ide.coffee index 6462859df2..f9c61b4114 100644 --- a/services/web/public/coffee/ide/services/ide.coffee +++ b/services/web/public/coffee/ide/services/ide.coffee @@ -3,11 +3,12 @@ define [ ], (App) -> # We create and provide this as service so that we can access the global ide # from within other parts of the angular app. - App.factory "ide", ["$http", "queuedHttp", "$modal", "$q", ($http, queuedHttp, $modal, $q) -> + App.factory "ide", ["$http", "queuedHttp", "$modal", "$q", "$filter", ($http, queuedHttp, $modal, $q, $filter) -> ide = {} ide.$http = $http ide.queuedHttp = queuedHttp ide.$q = $q + ide.$filter = $filter @recentEvents = [] ide.pushEvent = (type, meta = {}) => diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index c2e7ce6b26..7bb10f88f2 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -56,7 +56,8 @@ padding: 5px 10px; cursor: pointer; - .history-entry-selected & { + .history-entry-selected &, + .history-entry-label-selected & { background-color: @history-entry-selected-bg; color: #FFF; } @@ -68,7 +69,8 @@ margin-bottom: 3px; margin-right: 10px; white-space: nowrap; - .history-entry-selected & { + .history-entry-selected &, + .history-entry-label-selected & { color: @history-entry-selected-label-color; } } @@ -77,7 +79,8 @@ padding: 0 @padding-xs-horizontal 1px @padding-xs-horizontal; border: 0; background-color: @history-entry-label-bg-color; - .history-entry-selected & { + .history-entry-selected &, + .history-entry-label-selected & { background-color: @history-entry-selected-label-bg-color; } } @@ -99,7 +102,8 @@ border-radius: 0 9999px 9999px 0; &:hover { background-color: darken(@history-entry-label-bg-color, 8%); - .history-entry-selected & { + .history-entry-selected &, + .history-entry-label-selected & { background-color: darken(@history-entry-selected-label-bg-color, 8%); } } @@ -137,7 +141,8 @@ color: @history-highlight-color; font-weight: bold; word-break: break-all; - .history-entry-selected & { + .history-entry-selected &, + .history-entry-label-selected & { color: #FFF; } } @@ -169,6 +174,10 @@ .history-entry-label { .history-entry-details; padding: 7px 10px; + &.history-entry-label-selected { + background-color: @history-entry-selected-bg; + color: #FFF; + } } .history-file-tree-inner { From d6ab993519bff56a3c5082738e20aa9535fd269f Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 9 Aug 2018 12:00:53 +0100 Subject: [PATCH 048/104] Dont hide history entries while loading. --- services/web/app/views/project/editor/history/entriesListV2.pug | 1 - 1 file changed, 1 deletion(-) diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index e08de0effd..ca0c48b722 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -119,7 +119,6 @@ script(type="text/ng-template", id="historyEntriesListTpl") users="$ctrl.users" on-select="$ctrl.onEntrySelect({ selectedEntry: selectedEntry })" on-label-delete="$ctrl.onLabelDelete({ label: label })" - ng-show="!$ctrl.isLoading" ) .loading(ng-show="$ctrl.isLoading") i.fa.fa-spin.fa-refresh From 4f4ab579583f2c0b237a68e4dc5aed2fd4eefadc Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 9 Aug 2018 12:05:00 +0100 Subject: [PATCH 049/104] Use controller binding in the label tooltip. --- services/web/app/views/project/editor/history.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/views/project/editor/history.pug b/services/web/app/views/project/editor/history.pug index 2f3f317561..fec7327d6f 100644 --- a/services/web/app/views/project/editor/history.pug +++ b/services/web/app/views/project/editor/history.pug @@ -92,7 +92,7 @@ script(type="text/ng-template", id="historyLabelTooltipTpl") i.fa.fa-tag |  {{ $ctrl.labelText }} p.history-label-tooltip-owner #{translate("history_label_created_by")} {{ $ctrl.labelOwnerName }} - time.history-label-tooltip-datetime {{ labelCreationDateTime | formatDate }} + time.history-label-tooltip-datetime {{ $ctrl.labelCreationDateTime | formatDate }} script(type="text/ng-template", id="historyV2DeleteLabelModalTemplate") From 64a66aafd5452ddd9728511423186d974678360b Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 9 Aug 2018 12:16:36 +0100 Subject: [PATCH 050/104] Protect against reading the project members list too soon. --- .../ide/history/controllers/HistoryListController.coffee | 8 ++++++-- .../history/controllers/HistoryV2ListController.coffee | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee index cfd5bb75c5..6c9890c6e5 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee @@ -6,13 +6,17 @@ define [ App.controller "HistoryListController", ["$scope", "$modal", "ide", ($scope, $modal, ide) -> $scope.hoveringOverListSelectors = false - projectUsers = $scope.project.members.concat $scope.project.owner + $scope.projectUsers = [] + + $scope.$watch "project.members", (newVal) -> + if newVal? + $scope.projectUsers = newVal.concat $scope.project.owner # This method (and maybe the one below) will be removed soon. User details data will be # injected into the history API responses, so we won't need to fetch user data from other # local data structures. _getUserById = (id) -> - _.find projectUsers, (user) -> + _.find $scope.projectUsers, (user) -> curUserId = user?._id or user?.id curUserId == id diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee index f7c602733f..1635ec9f0a 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee @@ -7,8 +7,13 @@ define [ $scope.hoveringOverListSelectors = false $scope.listConfig = showOnlyLabelled: false - $scope.projectUsers = $scope.project.members.concat $scope.project.owner + $scope.projectUsers = [] + + $scope.$watch "project.members", (newVal) -> + if newVal? + $scope.projectUsers = newVal.concat $scope.project.owner + $scope.loadMore = () => ide.historyManager.fetchNextBatchOfUpdates() From 7830a5f69de7bd197c943e03b60acff8bd7ba143 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Thu, 9 Aug 2018 13:16:42 +0100 Subject: [PATCH 051/104] Update copy in v1 import modal now that more features are supported --- services/web/app/views/project/list/modals.pug | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/services/web/app/views/project/list/modals.pug b/services/web/app/views/project/list/modals.pug index 2bbde60e7b..aaacf660a6 100644 --- a/services/web/app/views/project/list/modals.pug +++ b/services/web/app/views/project/list/modals.pug @@ -345,13 +345,11 @@ script(type="text/ng-template", id="v1ImportModalTemplate") i.fa.fa-flask .v1-import-col h2.v1-import-title #[strong Warning:] Overleaf v2 is Experimental - p We are still working hard to bring some Overleaf v1 features to the v2 editor. In v2 there is: + p We are still working hard to bring some Overleaf v1 features to the v2 editor. In v2: ul - li No Journals and Services menu to submit directly to our partners yet - li No Rich Text (WYSIWYG) mode yet - li No linked files (to URLs or to files in other Overleaf projects) yet - li No Zotero and CiteULike integrations yet - li No labelled versions yet + li You may not be able to access all of your Labelled versions yet + li There are no Zotero and CiteULike integrations yet + li Some Journals and Services in the Submit menu don't support direct submissions yet p.row-spaced-small | If you currently use the Overleaf Git bridge with your v1 project, you can migrate your project to the Overleaf v2 GitHub integration. | From e80b52509b8899e4f5f5539f5a979abcd498cc49 Mon Sep 17 00:00:00 2001 From: Nate Stemen Date: Thu, 9 Aug 2018 08:53:49 -0400 Subject: [PATCH 052/104] fix silly paren issue for index var --- .../aceEditor/auto-complete/AutoCompleteManager.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 3be4369213..8fde8595cd 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 @@ -162,7 +162,8 @@ define [ cursorPosition = @editor.getCursorPosition() end = change.end {lineUpToCursor, commandFragment} = Helpers.getContext(@editor, end) - if (i = lineUpToCursor.indexOf('%') > -1 and lineUpToCursor[i-1] != '\\') + if ((i = lineUpToCursor.indexOf('%')) > -1 and lineUpToCursor[i-1] != '\\') + console.log lineUpToCursor, i return lastCharIsBackslash = lineUpToCursor.slice(-1) == "\\" lastTwoChars = lineUpToCursor.slice(-2) From 7e86218b212f4ca06f1793c5e0c0573a35636166 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 10 Aug 2018 11:28:17 +0100 Subject: [PATCH 053/104] Allow multiple events to trigger layout external resizes. --- services/web/app/views/project/editor.pug | 2 +- .../public/coffee/ide/directives/layout.coffee | 3 ++- .../coffee/ide/history/HistoryV2Manager.coffee | 17 ++++++++++------- .../web/public/coffee/ide/services/ide.coffee | 3 ++- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index 6d7d18687d..08124c334c 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -65,7 +65,7 @@ block content ng-class="{ 'ide-history-open' : (ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME) }", layout="main", ng-hide="state.loading", - resize-on="layout:chat:resize", + resize-on="layout:chat:resize,history:toggle", minimum-restore-size-west="130" custom-toggler-pane=hasFeature('custom-togglers') ? "'west'" : "false" custom-toggler-msg-when-open=hasFeature('custom-togglers') ? "'" + translate("tooltip_hide_filetree") + "'" : "false" diff --git a/services/web/public/coffee/ide/directives/layout.coffee b/services/web/public/coffee/ide/directives/layout.coffee index b4e2e7f83d..8c30943f39 100644 --- a/services/web/public/coffee/ide/directives/layout.coffee +++ b/services/web/public/coffee/ide/directives/layout.coffee @@ -112,7 +112,8 @@ define [ element.layout().resizeAll() if attrs.resizeOn? - scope.$on attrs.resizeOn, () -> onExternalResize() + for event in attrs.resizeOn.split "," + scope.$on event, () -> onExternalResize() if hasCustomToggler state = element.layout().readState() diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index 2e56d955ce..e7a2c071e3 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -27,6 +27,9 @@ define [ @hide() else @show() + @ide.$timeout () => + @$scope.$broadcast "history:toggle" + , 0 @$scope.toggleHistoryViewMode = () => if @$scope.history.viewMode == HistoryViewModes.COMPARE @@ -124,6 +127,10 @@ define [ return if @$scope.history.updates.length == 0 @selectUpdate @$scope.history.updates[0] + autoSelectLastLabel: () -> + return if @$scope.history.labels.length == 0 + @selectLabel @$scope.history.labels[0] + selectUpdate: (update) -> selectedUpdateIndex = @$scope.history.updates.indexOf update if selectedUpdateIndex == -1 @@ -135,10 +142,6 @@ define [ @$scope.history.updates[selectedUpdateIndex].selectedFrom = true @loadFileTreeForUpdate @$scope.history.updates[selectedUpdateIndex] - selectLastLabel = () -> - return if @$scope.history.labels.length == 0 - # TODO Select last label - selectedLabelFromUpdatesSelection: () -> nLabels = @$scope.history.selection.updates?[0]?.labels?.length if nLabels == 1 @@ -184,12 +187,12 @@ define [ @ide.$q.all requests .then (response) => updatesData = response.updates.data + if response.labels? + @$scope.history.labels = @_sortLabelsByVersionAndDate response.labels.data @_loadUpdates(updatesData.updates) @$scope.history.nextBeforeTimestamp = updatesData.nextBeforeTimestamp if !updatesData.nextBeforeTimestamp? @$scope.history.atEnd = true - if response.labels? - @$scope.history.labels = @_sortLabelsByVersionAndDate response.labels.data @$scope.history.loading = false _sortLabelsByVersionAndDate: (labels) -> @@ -354,7 +357,7 @@ define [ @autoSelectRecentUpdates() else if @$scope.history.showOnlyLabels - @selectLastLabel() + @autoSelectLastLabel() else @autoSelectLastUpdate() diff --git a/services/web/public/coffee/ide/services/ide.coffee b/services/web/public/coffee/ide/services/ide.coffee index f9c61b4114..24805b6270 100644 --- a/services/web/public/coffee/ide/services/ide.coffee +++ b/services/web/public/coffee/ide/services/ide.coffee @@ -3,12 +3,13 @@ define [ ], (App) -> # We create and provide this as service so that we can access the global ide # from within other parts of the angular app. - App.factory "ide", ["$http", "queuedHttp", "$modal", "$q", "$filter", ($http, queuedHttp, $modal, $q, $filter) -> + App.factory "ide", ["$http", "queuedHttp", "$modal", "$q", "$filter", "$timeout", ($http, queuedHttp, $modal, $q, $filter, $timeout) -> ide = {} ide.$http = $http ide.queuedHttp = queuedHttp ide.$q = $q ide.$filter = $filter + ide.$timeout = $timeout @recentEvents = [] ide.pushEvent = (type, meta = {}) => From 197e7ce8abfd522cd647a0619e0e543252306dd3 Mon Sep 17 00:00:00 2001 From: Tim Alby Date: Fri, 10 Aug 2018 13:40:25 +0200 Subject: [PATCH 054/104] refactor institutions getter --- .../Institutions/InstitutionsFeatures.coffee | 11 +++-- .../Institutions/InstitutionsGetter.coffee | 14 ++++++ .../InstitutionsFeaturesTests.coffee | 38 ++++++---------- .../InstitutionsGetterTests.coffee | 44 +++++++++++++++++++ 4 files changed, 77 insertions(+), 30 deletions(-) create mode 100644 services/web/app/coffee/Features/Institutions/InstitutionsGetter.coffee create mode 100644 services/web/test/unit/coffee/Institutions/InstitutionsGetterTests.coffee diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsFeatures.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsFeatures.coffee index 5c91058146..4ce233406b 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsFeatures.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsFeatures.coffee @@ -1,4 +1,4 @@ -UserGetter = require '../User/UserGetter' +InstitutionsGetter = require './InstitutionsGetter' PlansLocator = require '../Subscription/PlansLocator' Settings = require 'settings-sharelatex' logger = require 'logger-sharelatex' @@ -13,11 +13,10 @@ module.exports = InstitutionsFeatures = hasLicence: (userId, callback = (error, hasLicence) ->) -> - UserGetter.getUserFullEmails userId, (error, emailsData) -> + InstitutionsGetter.getConfirmedInstitutions userId, (error, institutions) -> return callback error if error? - affiliation = emailsData.find (emailData) -> - licence = emailData.affiliation?.institution?.licence - emailData.confirmedAt? and licence? and licence != 'free' + hasLicence = institutions.some (institution) -> + institution.licence and institution.licence != 'free' - callback(null, !!affiliation) + callback(null, hasLicence) diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsGetter.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsGetter.coffee new file mode 100644 index 0000000000..e5941dc777 --- /dev/null +++ b/services/web/app/coffee/Features/Institutions/InstitutionsGetter.coffee @@ -0,0 +1,14 @@ +UserGetter = require '../User/UserGetter' +logger = require 'logger-sharelatex' + +module.exports = InstitutionsGetter = + getConfirmedInstitutions: (userId, callback = (error, institutions) ->) -> + UserGetter.getUserFullEmails userId, (error, emailsData) -> + return callback error if error? + + confirmedInstitutions = emailsData.filter (emailData) -> + emailData.confirmedAt? and emailData.affiliation?.institution? + .map (emailData) -> + emailData.affiliation?.institution + + callback(null, confirmedInstitutions) diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsFeaturesTests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsFeaturesTests.coffee index 2304f2e5b7..8811f72c35 100644 --- a/services/web/test/unit/coffee/Institutions/InstitutionsFeaturesTests.coffee +++ b/services/web/test/unit/coffee/Institutions/InstitutionsFeaturesTests.coffee @@ -8,11 +8,11 @@ modulePath = require('path').join __dirname, '../../../../app/js/Features/Instit describe 'InstitutionsFeatures', -> beforeEach -> - @UserGetter = getUserFullEmails: sinon.stub() + @InstitutionsGetter = getConfirmedInstitutions: sinon.stub() @PlansLocator = findLocalPlanInSettings: sinon.stub() @institutionPlanCode = 'institution_plan_code' @InstitutionsFeatures = SandboxedModule.require modulePath, requires: - '../User/UserGetter': @UserGetter + './InstitutionsGetter': @InstitutionsGetter '../Subscription/PlansLocator': @PlansLocator 'settings-sharelatex': institutionPlanCode: @institutionPlanCode 'logger-sharelatex': @@ -23,47 +23,37 @@ describe 'InstitutionsFeatures', -> describe "hasLicence", -> it 'should handle error', (done)-> - @UserGetter.getUserFullEmails.yields(new Error('Nope')) + @InstitutionsGetter.getConfirmedInstitutions.yields(new Error('Nope')) @InstitutionsFeatures.hasLicence @userId, (error, hasLicence) -> expect(error).to.exist done() - it 'should return false if user has no affiliations', (done) -> - @UserGetter.getUserFullEmails.yields(null, []) - @InstitutionsFeatures.hasLicence @userId, (error, hasLicence) -> - expect(error).to.not.exist - expect(hasLicence).to.be.false - done() - it 'should return false if user has no confirmed affiliations', (done) -> - affiliations = [ - { confirmedAt: null, affiliation: institution: { licence: 'pro_plus' } } - ] - @UserGetter.getUserFullEmails.yields(null, affiliations) + institutions = [] + @InstitutionsGetter.getConfirmedInstitutions.yields(null, institutions) @InstitutionsFeatures.hasLicence @userId, (error, hasLicence) -> expect(error).to.not.exist expect(hasLicence).to.be.false done() it 'should return false if user has no paid affiliations', (done) -> - affiliations = [ - { confirmedAt: new Date(), affiliation: institution: { licence: 'free' } } + institutions = [ + { licence: 'free' } ] - @UserGetter.getUserFullEmails.yields(null, affiliations) + @InstitutionsGetter.getConfirmedInstitutions.yields(null, institutions) @InstitutionsFeatures.hasLicence @userId, (error, hasLicence) -> expect(error).to.not.exist expect(hasLicence).to.be.false done() it 'should return true if user has confirmed paid affiliation', (done)-> - affiliations = [ - { confirmedAt: new Date(), affiliation: institution: { licence: 'pro_plus' } } - { confirmedAt: new Date(), affiliation: institution: { licence: 'free' } } - { confirmedAt: null, affiliation: institution: { licence: 'pro' } } - { confirmedAt: null, affiliation: institution: { licence: null } } - { confirmedAt: new Date(), affiliation: institution: {} } + institutions = [ + { licence: 'pro_plus' } + { licence: 'free' } + { licence: 'pro' } + { licence: null } ] - @UserGetter.getUserFullEmails.yields(null, affiliations) + @InstitutionsGetter.getConfirmedInstitutions.yields(null, institutions) @InstitutionsFeatures.hasLicence @userId, (error, hasLicence) -> expect(error).to.not.exist expect(hasLicence).to.be.true diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsGetterTests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsGetterTests.coffee new file mode 100644 index 0000000000..62c57cdaa9 --- /dev/null +++ b/services/web/test/unit/coffee/Institutions/InstitutionsGetterTests.coffee @@ -0,0 +1,44 @@ +SandboxedModule = require('sandboxed-module') +require('chai').should() +expect = require('chai').expect +sinon = require('sinon') +modulePath = require('path').join __dirname, '../../../../app/js/Features/Institutions/InstitutionsGetter.js' + +describe 'InstitutionsGetter', -> + beforeEach -> + @UserGetter = getUserFullEmails: sinon.stub() + @InstitutionsGetter = SandboxedModule.require modulePath, requires: + '../User/UserGetter': @UserGetter + 'logger-sharelatex': + log:-> console.log(arguments) + err:-> + + @userId = '12345abcde' + + describe "getConfirmedInstitutions", -> + it 'filters unconfirmed emails', (done) -> + @userEmails = [ + { confirmedAt: null, affiliation: institution: { id: 123 } } + { confirmedAt: new Date(), affiliation: institution: { id: 456 } } + { confirmedAt: new Date(), affiliation: null } + { confirmedAt: new Date(), affiliation: institution: null } + ] + @UserGetter.getUserFullEmails.yields(null, @userEmails) + @InstitutionsGetter.getConfirmedInstitutions @userId, (error, institutions) -> + expect(error).to.not.exist + institutions.length.should.equal 1 + institutions[0].id.should.equal 456 + done() + + it 'should handle empty response', (done) -> + @UserGetter.getUserFullEmails.yields(null, []) + @InstitutionsGetter.getConfirmedInstitutions @userId, (error, institutions) -> + expect(error).to.not.exist + institutions.length.should.equal 0 + done() + + it 'should handle error', (done) -> + @UserGetter.getUserFullEmails.yields(new Error('Nope')) + @InstitutionsGetter.getConfirmedInstitutions @userId, (error, institutions) -> + expect(error).to.exist + done() From c13e0264e555ec7bb190eb2bd4221cf85c6ee04b Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 10 Aug 2018 14:07:35 +0100 Subject: [PATCH 055/104] Fix a few bugs related to keeping the selection when moving from labels to full history. --- .../ide/history/HistoryV2Manager.coffee | 84 ++++++++++++++----- .../HistoryV2ListController.coffee | 62 -------------- 2 files changed, 62 insertions(+), 84 deletions(-) diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index e7a2c071e3..e8a3f46f24 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -53,8 +53,15 @@ define [ @reloadDiff() @$scope.$watch "history.showOnlyLabels", (showOnlyLabels, prevVal) => - if showOnlyLabels? and showOnlyLabels != prevVal and showOnlyLabels - @selectedLabelFromUpdatesSelection() + if showOnlyLabels? and showOnlyLabels != prevVal + if showOnlyLabels + @selectedLabelFromUpdatesSelection() + else + if @$scope.history.selection.updates.length == 0 + @autoSelectLastUpdate() + + @$scope.$watch "history.updates.length", () => + @recalculateSelectedUpdates() show: () -> @$scope.ui.view = "history" @@ -95,10 +102,9 @@ define [ _csrf: window.csrfToken }) - loadFileTreeForUpdate: (update) -> - {fromV, toV} = update + loadFileTreeForVersion: (version) -> url = "/project/#{@$scope.project_id}/filetree/diff" - query = [ "from=#{toV}", "to=#{toV}" ] + query = [ "from=#{version}", "to=#{version}" ] url += "?" + query.join("&") @$scope.history.loadingFileTree = true @$scope.history.selectedFile = null @@ -140,33 +146,66 @@ define [ update.selectedFrom = false @$scope.history.updates[selectedUpdateIndex].selectedTo = true @$scope.history.updates[selectedUpdateIndex].selectedFrom = true - @loadFileTreeForUpdate @$scope.history.updates[selectedUpdateIndex] + @recalculateSelectedUpdates() + @loadFileTreeForVersion @$scope.history.updates[selectedUpdateIndex].toV selectedLabelFromUpdatesSelection: () -> - nLabels = @$scope.history.selection.updates?[0]?.labels?.length - if nLabels == 1 + # Get the number of labels associated with the currently selected update + nSelectedLabels = @$scope.history.selection.updates?[0]?.labels?.length + # If the currently selected update has no labels, select the last one (version-wise) + if nSelectedLabels == 0 + @autoSelectLastLabel() + # If the update has one label, select it + else if nSelectedLabels == 1 @selectLabel @$scope.history.selection.updates[0].labels[0] - else if nLabels > 1 + # If there are multiple labels for the update, select the latest + else if nSelectedLabels > 1 sortedLabels = @ide.$filter("orderBy")(@$scope.history.selection.updates[0].labels, '-created_at') lastLabelFromUpdate = sortedLabels[0] @selectLabel lastLabelFromUpdate selectLabel: (labelToSelect) -> - labelToSelectIndex = -1 - for update, i in @$scope.history.updates - if update.toV == labelToSelect.version - labelToSelectIndex = i - break - if labelToSelectIndex == -1 - labelToSelectIndex = 0 + updateToSelect = null + alreadySelected = false for update in @$scope.history.updates - update.selectedTo = false - update.selectedFrom = false + if update.toV == labelToSelect.version + updateToSelect = update + break + for label in @$scope.history.labels - label.selected = (labelToSelect.id == label.id) - @$scope.history.updates[labelToSelectIndex].selectedTo = true - @$scope.history.updates[labelToSelectIndex].selectedFrom = true - @loadFileTreeForUpdate @$scope.history.updates[labelToSelectIndex] + matchingLabel = (labelToSelect.id == label.id) + if matchingLabel and label.selected + alreadySelected = true + label.selected = matchingLabel + + if alreadySelected + return + + if updateToSelect? + @selectUpdate updateToSelect + else + @$scope.history.selection.updates = [] + @loadFileTreeForVersion labelToSelect.version + + recalculateSelectedUpdates: () -> + beforeSelection = true + afterSelection = false + @$scope.history.selection.updates = [] + for update in @$scope.history.updates + if update.selectedTo + inSelection = true + beforeSelection = false + + update.beforeSelection = beforeSelection + update.inSelection = inSelection + update.afterSelection = afterSelection + + if inSelection + @$scope.history.selection.updates.push update + + if update.selectedFrom + inSelection = false + afterSelection = true BATCH_SIZE: 10 fetchNextBatchOfUpdates: () -> @@ -199,6 +238,7 @@ define [ @ide.$filter("orderBy")(labels, [ '-version', '-created_at' ]) loadFileAtPointInTime: () -> + console.log @$scope.history.selection.pathname, @$scope.history.selection.updates pathname = @$scope.history.selection.pathname toV = @$scope.history.selection.updates[0].toV url = "/project/#{@$scope.project_id}/diff" diff --git a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee index 7129e0374c..55c2d791a9 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryV2ListController.coffee @@ -14,11 +14,9 @@ define [ $scope.handleEntrySelect = (entry) -> ide.historyManager.selectUpdate(entry) - $scope.recalculateSelectedUpdates() $scope.handleLabelSelect = (label) -> ide.historyManager.selectLabel(label) - $scope.recalculateSelectedUpdates() $scope.handleLabelDelete = (labelDetails) -> $modal.open( @@ -27,64 +25,4 @@ define [ resolve: labelDetails: () -> labelDetails ) - - $scope.recalculateSelectedUpdates = () -> - beforeSelection = true - afterSelection = false - $scope.history.selection.updates = [] - for update in $scope.history.updates - if update.selectedTo - inSelection = true - beforeSelection = false - - update.beforeSelection = beforeSelection - update.inSelection = inSelection - update.afterSelection = afterSelection - - if inSelection - $scope.history.selection.updates.push update - - if update.selectedFrom - inSelection = false - afterSelection = true - - $scope.recalculateHoveredUpdates = () -> - hoverSelectedFrom = false - hoverSelectedTo = false - for update in $scope.history.updates - # Figure out whether the to or from selector is hovered over - if update.hoverSelectedFrom - hoverSelectedFrom = true - if update.hoverSelectedTo - hoverSelectedTo = true - - if hoverSelectedFrom - # We want to 'hover select' everything between hoverSelectedFrom and selectedTo - inHoverSelection = false - for update in $scope.history.updates - if update.selectedTo - update.hoverSelectedTo = true - inHoverSelection = true - update.inHoverSelection = inHoverSelection - if update.hoverSelectedFrom - inHoverSelection = false - if hoverSelectedTo - # We want to 'hover select' everything between hoverSelectedTo and selectedFrom - inHoverSelection = false - for update in $scope.history.updates - if update.hoverSelectedTo - inHoverSelection = true - update.inHoverSelection = inHoverSelection - if update.selectedFrom - update.hoverSelectedFrom = true - inHoverSelection = false - - $scope.resetHoverState = () -> - for update in $scope.history.updates - delete update.hoverSelectedFrom - delete update.hoverSelectedTo - delete update.inHoverSelection - - $scope.$watch "history.updates.length", () -> - $scope.recalculateSelectedUpdates() ] \ No newline at end of file From 035ff0a1f0b0984560d9589f289db86fa66d8dce Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 10 Aug 2018 15:57:10 +0100 Subject: [PATCH 056/104] More bugfixing. --- .../public/coffee/ide/history/HistoryV2Manager.coffee | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index e8a3f46f24..ddd098a5f5 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -238,9 +238,15 @@ define [ @ide.$filter("orderBy")(labels, [ '-version', '-created_at' ]) loadFileAtPointInTime: () -> - console.log @$scope.history.selection.pathname, @$scope.history.selection.updates pathname = @$scope.history.selection.pathname - toV = @$scope.history.selection.updates[0].toV + if @$scope.history.selection.updates?[0]? + toV = @$scope.history.selection.updates[0].toV + else + for label in @$scope.history.labels or [] + if label.selected + toV = label.version + if !toV? + return url = "/project/#{@$scope.project_id}/diff" query = ["pathname=#{encodeURIComponent(pathname)}", "from=#{toV}", "to=#{toV}"] url += "?" + query.join("&") From 0f8fe53bc90fccb4ff0ef1720aaf0c8e7a620b93 Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 1 Aug 2018 09:37:14 +0100 Subject: [PATCH 057/104] Allow confirmed_at date to be specified --- services/web/app/coffee/Features/User/UserUpdater.coffee | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index 8d5e3658f9..544f37a82e 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -152,7 +152,10 @@ module.exports = UserUpdater = else return callback new Error("non-success code from v1: #{response.statusCode}") - confirmEmail: (userId, email, callback) -> + confirmEmail: (userId, email, confirmedAt, callback) -> + if arguments.length == 3 + callback = confirmedAt + confirmedAt = new Date() email = EmailHelper.parseEmail(email) return callback(new Error('invalid email')) if !email? logger.log {userId, email}, 'confirming user email' @@ -166,7 +169,7 @@ module.exports = UserUpdater = 'emails.email': email update = $set: - 'emails.$.confirmedAt': new Date() + 'emails.$.confirmedAt': confirmedAt @updateUser query, update, (error, res) -> return callback(error) if error? logger.log {res, userId, email}, "tried to confirm email" From 19dfe5fc8bfe2778a253cfb41540e34ed3413cfc Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 13 Aug 2018 10:22:23 +0100 Subject: [PATCH 058/104] intial version of user setting for texlive imageName --- .../coffee/Features/Editor/EditorController.coffee | 7 +++++++ .../coffee/Features/Project/ProjectController.coffee | 5 +++++ .../Features/Project/ProjectEditorHandler.coffee | 3 ++- .../Features/Project/ProjectOptionsHandler.coffee | 10 ++++++++++ services/web/app/views/project/editor/left-menu.pug | 9 +++++++++ services/web/config/settings.defaults.coffee | 11 +++++++++++ .../settings/controllers/SettingsController.coffee | 11 +++++++++++ 7 files changed, 55 insertions(+), 1 deletion(-) diff --git a/services/web/app/coffee/Features/Editor/EditorController.coffee b/services/web/app/coffee/Features/Editor/EditorController.coffee index 846042312d..7dad02bde9 100644 --- a/services/web/app/coffee/Features/Editor/EditorController.coffee +++ b/services/web/app/coffee/Features/Editor/EditorController.coffee @@ -163,6 +163,13 @@ module.exports = EditorController = EditorRealTimeController.emitToRoom project_id, 'compilerUpdated', compiler callback() + setImageName : (project_id, imageName, callback = (err) ->) -> + ProjectOptionsHandler.setImageName project_id, imageName, (err) -> + return callback(err) if err? + logger.log imageName:imageName, project_id:project_id, "setting imageName" + EditorRealTimeController.emitToRoom project_id, 'imageNameUpdated', imageName + callback() + setSpellCheckLanguage : (project_id, languageCode, callback = (err) ->) -> ProjectOptionsHandler.setSpellCheckLanguage project_id, languageCode, (err) -> return callback(err) if err? diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index bb16855885..f3d12bf5c5 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -49,6 +49,10 @@ module.exports = ProjectController = jobs.push (callback) -> editorController.setCompiler project_id, req.body.compiler, callback + if req.body.imageName? + jobs.push (callback) -> + editorController.setImageName project_id, req.body.imageName, callback + if req.body.name? jobs.push (callback) -> editorController.renameProject project_id, req.body.name, callback @@ -347,6 +351,7 @@ module.exports = ProjectController = useV2History: !!project.overleaf?.history?.display richTextEnabled: Features.hasFeature('rich-text') showTestControls: req.query?.tc == 'true' || user.isAdmin + allowedImageNames: Settings.allowedImageNames || [] timer.done() _buildProjectList: (allProjects, v1Projects = [])-> diff --git a/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee index 410dbf0351..f81c59c399 100644 --- a/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee @@ -1,5 +1,5 @@ _ = require("underscore") - +Path = require 'path' module.exports = ProjectEditorHandler = trackChangesAvailable: false @@ -20,6 +20,7 @@ module.exports = ProjectEditorHandler = members: [] invites: invites tokens: project.tokens + imageName: if project.imageName? then Path.basename(project.imageName) else undefined if !result.invites? result.invites = [] diff --git a/services/web/app/coffee/Features/Project/ProjectOptionsHandler.coffee b/services/web/app/coffee/Features/Project/ProjectOptionsHandler.coffee index 0a0b02e40e..456b164683 100644 --- a/services/web/app/coffee/Features/Project/ProjectOptionsHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectOptionsHandler.coffee @@ -17,6 +17,16 @@ module.exports = if callback? callback() + setImageName : (project_id, imageName, callback = ()->)-> + logger.log project_id:project_id, imageName:imageName, "setting the imageName" + imageName = imageName.toLowerCase() + if ! _.some(settings.allowedImageNames, (allowed) -> imageName is allowed.imageName) + return callback() + conditions = {_id:project_id} + update = {imageName: settings.imageRoot + '/' + imageName} + Project.update conditions, update, {}, (err)-> + if callback? + callback() setSpellCheckLanguage: (project_id, languageCode, callback = ()->)-> logger.log project_id:project_id, languageCode:languageCode, "setting the spell check language" diff --git a/services/web/app/views/project/editor/left-menu.pug b/services/web/app/views/project/editor/left-menu.pug index 2cedc0b37e..bbfa4c480c 100644 --- a/services/web/app/views/project/editor/left-menu.pug +++ b/services/web/app/views/project/editor/left-menu.pug @@ -188,6 +188,15 @@ aside#left-menu.full-size( option(value="pdfjs") #{translate("built_in")} option(value="native") #{translate("native")} + if (getSessionUser() && getSessionUser().isAdmin && typeof(allowedImageNames) !== 'undefined' && allowedImageNames.length > 0) + .form-controls(ng-show="permissions.write") + label(for="imageName") #{translate("TeXLive")} + select( + name="imageName" + ng-model="project.imageName" + ) + each image in allowedImageNames + option(value=image.imageName) #{image.imageDesc} h4 #{translate("hotkeys")} ul.list-unstyled.nav diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 1306ce91c2..d6e6ecb690 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -472,3 +472,14 @@ module.exports = settings = autoCompile: everyone: 100 standard: 25 + + # currentImage: "texlive-full:2017.1" + # imageRoot: "" # without any trailing slash + + # allowedImageNames: [ + # {imageName: 'texlive-full:2017.1', imageDesc: 'TeXLive 2017'} + # {imageName: 'wl_texlive:2018.1', imageDesc: 'Legacy OL TeXLive 2015'} + # {imageName: 'texlive-full:2016.1', imageDesc: 'Legacy SL TeXLive 2016'} + # {imageName: 'texlive-full:2015.1', imageDesc: 'Legacy SL TeXLive 2015'} + # {imageName: 'texlive-full:2014.2', imageDesc: 'Legacy SL TeXLive 2014.2'} + # ] \ No newline at end of file diff --git a/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee b/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee index 9920372aab..2e8c09739e 100644 --- a/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee +++ b/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee @@ -67,6 +67,11 @@ define [ if oldCompiler? and compiler != oldCompiler settings.saveProjectSettings({compiler: compiler}) + $scope.$watch "project.imageName", (imageName, oldImageName) => + return if @ignoreUpdates + if oldImageName? and imageName != oldImageName + settings.saveProjectSettings({imageName: imageName}) + $scope.$watch "project.rootDoc_id", (rootDoc_id, oldRootDoc_id) => return if @ignoreUpdates # don't save on initialisation, Angular passes oldRootDoc_id as @@ -83,6 +88,12 @@ define [ $scope.project.compiler = compiler delete @ignoreUpdates + ide.socket.on "imageNameUpdated", (imageName) => + @ignoreUpdates = true + $scope.$apply () => + $scope.project.imageName = imageName + delete @ignoreUpdates + ide.socket.on "spellCheckLanguageUpdated", (languageCode) => @ignoreUpdates = true $scope.$apply () => From c2828c8ca539031230306f405a1d5b5410917aed Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 13 Aug 2018 10:48:11 +0100 Subject: [PATCH 059/104] add unit tests --- .../coffee/Editor/EditorControllerTests.coffee | 14 ++++++++++++++ .../Project/ProjectControllerTests.coffee | 12 ++++++++++++ .../Project/ProjectOptionsHandlerTests.coffee | 18 ++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee b/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee index 703a03d14f..f1bf3792a2 100644 --- a/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee +++ b/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee @@ -32,6 +32,7 @@ describe "EditorController", -> '../Project/ProjectEntityUpdateHandler' : @ProjectEntityUpdateHandler = {} '../Project/ProjectOptionsHandler' : @ProjectOptionsHandler = setCompiler: sinon.stub().yields() + setImageName: sinon.stub().yields() setSpellCheckLanguage: sinon.stub().yields() '../Project/ProjectDetailsHandler': @ProjectDetailsHandler = setProjectDescription: sinon.stub().yields() @@ -377,6 +378,19 @@ describe "EditorController", -> .calledWith(@project_id, "compilerUpdated", @compiler) .should.equal true + describe "setImageName", -> + beforeEach -> + @imageName = "texlive-1234.5" + @EditorController.setImageName @project_id, @imageName, @callback + + it "should send the new imageName and project id to the project options handler", -> + @ProjectOptionsHandler.setImageName + .calledWith(@project_id, @imageName) + .should.equal true + @EditorRealTimeController.emitToRoom + .calledWith(@project_id, "imageNameUpdated", @imageName) + .should.equal true + describe "setSpellCheckLanguage", -> beforeEach -> @languageCode = "fr" diff --git a/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee b/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee index 1acd1c8128..46e54501c9 100644 --- a/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee @@ -145,6 +145,18 @@ describe "ProjectController", -> done() @ProjectController.updateProjectSettings @req, @res + it "should update the imageName", (done) -> + @EditorController.setImageName = sinon.stub().callsArg(2) + @req.body = + imageName: @imageName = "texlive-1234.5" + @res.sendStatus = (code) => + @EditorController.setImageName + .calledWith(@project_id, @imageName) + .should.equal true + code.should.equal 204 + done() + @ProjectController.updateProjectSettings @req, @res + it "should update the spell check language", (done) -> @EditorController.setSpellCheckLanguage = sinon.stub().callsArg(2) @req.body = diff --git a/services/web/test/unit/coffee/Project/ProjectOptionsHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectOptionsHandlerTests.coffee index 825822c236..435dba9dbc 100644 --- a/services/web/test/unit/coffee/Project/ProjectOptionsHandlerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectOptionsHandlerTests.coffee @@ -19,6 +19,11 @@ describe 'creating a project', -> {name: "English", code: "en"} {name: "French", code: "fr"} ] + imageRoot: "docker-repo/subdir" + allowedImageNames: [ + {imageName: "texlive-0000.0", imageDesc: "test image 0"} + {imageName: "texlive-1234.5", imageDesc: "test image 1"} + ] 'logger-sharelatex': log:-> err:-> @@ -37,6 +42,19 @@ describe 'creating a project', -> @projectModel.update.called.should.equal false done() + describe 'Setting the imageName', -> + it 'should perform and update on mongo', (done)-> + @handler.setImageName project_id, "texlive-1234.5", (err)=> + args = @projectModel.update.args[0] + args[0]._id.should.equal project_id + args[1].imageName.should.equal "docker-repo/subdir/texlive-1234.5" + done() + @projectModel.update.args[0][3]() + + it 'should not perform and update on mongo if it is not a reconised compiler', (done)-> + @handler.setImageName project_id, "something", (err)=> + @projectModel.update.called.should.equal false + done() describe "setting the spellCheckLanguage", -> From 2ee42af1de4fd6e0408840050041d8aaf2b8efa1 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 13 Aug 2018 13:39:25 +0100 Subject: [PATCH 060/104] Change strategy to store selected label; improve the history toolbar layout to accomodate larger labels; fix issues with layout not being updated when changing history view modes. --- .../project/editor/history/entriesListV2.pug | 3 +- .../project/editor/history/toolbarV2.pug | 37 +++++++++++-------- .../ide/history/HistoryV2Manager.coffee | 31 +++++++++------- .../components/historyLabelsList.coffee | 3 +- .../stylesheets/app/editor/history-v2.less | 32 +++++++++------- 5 files changed, 62 insertions(+), 44 deletions(-) diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index 345eab8dd6..21c2438742 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -20,6 +20,7 @@ aside.change-list( current-user="user" users="projectUsers" is-loading="history.loading" + selected-label="history.selection.label" on-label-select="handleLabelSelect(label)" on-label-delete="handleLabelDelete(label)" ) @@ -209,7 +210,7 @@ script(type="text/ng-template", id="historyLabelsListTpl") .history-entry-label( ng-repeat="label in $ctrl.labels track by label.id" ng-click="$ctrl.onLabelSelect({ label: label })" - ng-class="{ 'history-entry-label-selected': label.selected }" + ng-class="{ 'history-entry-label-selected': label.id === $ctrl.selectedLabel.id }" ) history-label( show-tooltip="false" diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index 6abe167f42..4e4b6f7ae3 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -2,24 +2,31 @@ ng-controller="HistoryV2ToolbarController" ng-if="ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME" ) - span(ng-show="history.loadingFileTree") + span.history-toolbar-selected-version(ng-show="history.loadingFileTree") i.fa.fa-spin.fa-refresh |    #{translate("loading")}... - span(ng-show="!history.loadingFileTree") #{translate("browsing_project_as_of")}  + span.history-toolbar-selected-version( + ng-show="!history.loadingFileTree && !history.showOnlyLabels" + ) #{translate("browsing_project_as_of")}  time.history-toolbar-time {{ history.selection.updates[0].meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }} - button.history-toolbar-btn( - ng-click="showAddLabelDialog();" - ng-if="!history.showOnlyLabels" - ng-disabled="history.loadingFileTree" - ) - i.fa.fa-tag - |  #{translate("history_label_this_version")} - button.history-toolbar-btn( - ng-click="toggleHistoryViewMode();" - ng-disabled="history.loadingFileTree" - ) - i.fa.fa-exchange - |  #{translate("compare_to_another_version")} + span.history-toolbar-selected-version( + ng-show="!history.loadingFileTree && history.showOnlyLabels && history.selection.label" + ) Browsing project version labelled + span.history-toolbar-selected-label "{{ history.selection.label.comment }}" + div.history-toolbar-actions + button.history-toolbar-btn( + ng-click="showAddLabelDialog();" + ng-if="!history.showOnlyLabels" + ng-disabled="history.loadingFileTree" + ) + i.fa.fa-tag + |  #{translate("history_label_this_version")} + button.history-toolbar-btn( + ng-click="toggleHistoryViewMode();" + ng-disabled="history.loadingFileTree" + ) + i.fa.fa-exchange + |  #{translate("compare_to_another_version")} .history-toolbar-entries-list toggle-switch( diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index ddd098a5f5..aa4ba09d62 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -38,6 +38,9 @@ define [ else @reset() @$scope.history.viewMode = HistoryViewModes.COMPARE + @ide.$timeout () => + @$scope.$broadcast "history:toggle" + , 0 @$scope.$watch "history.selection.updates", (updates) => if @$scope.history.viewMode == HistoryViewModes.COMPARE @@ -57,6 +60,7 @@ define [ if showOnlyLabels @selectedLabelFromUpdatesSelection() else + @$scope.history.selection.label = null if @$scope.history.selection.updates.length == 0 @autoSelectLastUpdate() @@ -79,6 +83,7 @@ define [ nextBeforeTimestamp: null atEnd: false selection: { + label: null updates: [] docs: {} pathname: null @@ -166,21 +171,17 @@ define [ selectLabel: (labelToSelect) -> updateToSelect = null - alreadySelected = false + + if @_isLabelSelected labelToSelect + # Label already selected + return + for update in @$scope.history.updates if update.toV == labelToSelect.version updateToSelect = update break - for label in @$scope.history.labels - matchingLabel = (labelToSelect.id == label.id) - if matchingLabel and label.selected - alreadySelected = true - label.selected = matchingLabel - - if alreadySelected - return - + @$scope.history.selection.label = labelToSelect if updateToSelect? @selectUpdate updateToSelect else @@ -241,10 +242,9 @@ define [ pathname = @$scope.history.selection.pathname if @$scope.history.selection.updates?[0]? toV = @$scope.history.selection.updates[0].toV - else - for label in @$scope.history.labels or [] - if label.selected - toV = label.version + else if @$scope.history.selection.label? + toV = @$scope.history.selection.label.version + if !toV? return url = "/project/#{@$scope.project_id}/diff" @@ -316,6 +316,9 @@ define [ }).then (response) => @_deleteLabelLocally label + _isLabelSelected: (label) -> + label.id == @$scope.history.selection.label?.id + _deleteLabelLocally: (labelToDelete) -> for update, i in @$scope.history.updates if update.toV == labelToDelete.version diff --git a/services/web/public/coffee/ide/history/components/historyLabelsList.coffee b/services/web/public/coffee/ide/history/components/historyLabelsList.coffee index 44c9a54742..75486b9081 100644 --- a/services/web/public/coffee/ide/history/components/historyLabelsList.coffee +++ b/services/web/public/coffee/ide/history/components/historyLabelsList.coffee @@ -16,7 +16,7 @@ define [ ctrl.getUserCSSStyle = (user, label) -> curUserId = user?._id or user?.id hue = ColorManager.getHueForUserId(curUserId) or 100 - if label.selected + if label.id == ctrl.selectedLabel?.id color : "#FFF" else color: "hsl(#{ hue }, 70%, 50%)" @@ -28,6 +28,7 @@ define [ users: "<" currentUser: "<" isLoading: "<" + selectedLabel: "<" onLabelSelect: "&" onLabelDelete: "&" controller: historyLabelsListController diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index 7bb10f88f2..cfefbb462c 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -15,22 +15,28 @@ .history-toolbar when (@is-overleaf = false) { border-bottom: @toolbar-border-bottom; } - .history-toolbar-time { - font-weight: bold; + .history-toolbar-selected-version { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } - .history-toolbar-btn { - .btn; - .btn-info; - .btn-xs; - padding-left: @padding-small-horizontal; - padding-right: @padding-small-horizontal; - margin-left: (@line-height-computed / 2); + .history-toolbar-time, + .history-toolbar-selected-label { + font-weight: bold; + } + .history-toolbar-actions { + flex-grow: 1; } - + .history-toolbar-btn { + .btn; + .btn-info; + .btn-xs; + padding-left: @padding-small-horizontal; + padding-right: @padding-small-horizontal; + margin-left: (@line-height-computed / 2); + } .history-toolbar-entries-list { - position: absolute; - right: 0; - width: @changesListWidth; + flex: 0 0 @changesListWidth; padding: 0 10px; border-left: 1px solid @editor-border-color; } From bfb2f636cdf50f0b5cf91019b7c51892af1893aa Mon Sep 17 00:00:00 2001 From: Tim Alby Date: Mon, 13 Aug 2018 14:59:39 +0200 Subject: [PATCH 061/104] enable affiliations UI --- .../web/app/coffee/Features/User/UserPagesController.coffee | 1 - services/web/app/views/user/settings.pug | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/services/web/app/coffee/Features/User/UserPagesController.coffee b/services/web/app/coffee/Features/User/UserPagesController.coffee index 2a7ed62d02..5e6ea7d62b 100644 --- a/services/web/app/coffee/Features/User/UserPagesController.coffee +++ b/services/web/app/coffee/Features/User/UserPagesController.coffee @@ -68,7 +68,6 @@ module.exports = shouldAllowEditingDetails: shouldAllowEditingDetails languages: Settings.languages, accountSettingsTabActive: true - showAffiliationsUI: (req.query?.aff == "true") or false sessionsPage: (req, res, next) -> user = AuthenticationController.getSessionUser(req) diff --git a/services/web/app/views/user/settings.pug b/services/web/app/views/user/settings.pug index a43e81c12c..e4da83c1db 100644 --- a/services/web/app/views/user/settings.pug +++ b/services/web/app/views/user/settings.pug @@ -9,7 +9,7 @@ block content .page-header h1 #{translate("account_settings")} .account-settings(ng-controller="AccountSettingsController", ng-cloak) - if locals.showAffiliationsUI && hasFeature('affiliations') + if hasFeature('affiliations') include settings/user-affiliations form-messages(for="settingsForm") @@ -22,7 +22,7 @@ block content h3 #{translate("update_account_info")} form(async-form="settings", name="settingsForm", method="POST", action="/user/settings", novalidate) input(type="hidden", name="_csrf", value=csrfToken) - if !(locals.showAffiliationsUI && hasFeature('affiliations')) + if !hasFeature('affiliations') if !externalAuthenticationSystemUsed() .form-group label(for='email') #{translate("email")} From 085c2529d979f7cd7987c6ef276e859065218e1b Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Mon, 13 Aug 2018 12:13:44 +0100 Subject: [PATCH 062/104] Inject isOverleaf setting globally --- services/web/app/coffee/infrastructure/ExpressLocals.coffee | 4 ++++ services/web/app/views/layout.pug | 1 + 2 files changed, 5 insertions(+) diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 5c71afbe5d..f31d94b8e2 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -333,3 +333,7 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)-> defaultLineHeight : if isOl then 'normal' else 'compact' renderAnnouncements : !isOl next() + + webRouter.use (req, res, next) -> + res.locals.exposedSettings = + isOverleaf: Settings.overleaf? diff --git a/services/web/app/views/layout.pug b/services/web/app/views/layout.pug index 9e633667eb..d820f2b4a1 100644 --- a/services/web/app/views/layout.pug +++ b/services/web/app/views/layout.pug @@ -70,6 +70,7 @@ html(itemscope, itemtype='http://schema.org/Product') window.systemMessages = !{JSON.stringify(systemMessages).replace(/\//g, '\\/')}; window.ab = {}; window.user_id = '#{getLoggedInUserId()}'; + window.exposedSettings = JSON.parse('!{JSON.stringify(exposedSettings).replace(/\//g, "\\/")}'); - if (typeof(settings.algolia) != "undefined") script. From db16fad6c9d8f56877a643df9f593697651087f4 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Mon, 13 Aug 2018 12:32:32 +0100 Subject: [PATCH 063/104] Fix --- services/web/app/coffee/infrastructure/ExpressLocals.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index f31d94b8e2..d4444b3542 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -335,5 +335,6 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)-> next() webRouter.use (req, res, next) -> - res.locals.exposedSettings = + res.locals.ExposedSettings = isOverleaf: Settings.overleaf? + next() From 274ac8b1ba48be1c19b8826f9366eb23307d5f89 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Mon, 13 Aug 2018 12:32:49 +0100 Subject: [PATCH 064/104] Another fix --- services/web/app/views/layout.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/views/layout.pug b/services/web/app/views/layout.pug index d820f2b4a1..15d3150035 100644 --- a/services/web/app/views/layout.pug +++ b/services/web/app/views/layout.pug @@ -70,7 +70,7 @@ html(itemscope, itemtype='http://schema.org/Product') window.systemMessages = !{JSON.stringify(systemMessages).replace(/\//g, '\\/')}; window.ab = {}; window.user_id = '#{getLoggedInUserId()}'; - window.exposedSettings = JSON.parse('!{JSON.stringify(exposedSettings).replace(/\//g, "\\/")}'); + window.ExposedSettings = JSON.parse('!{JSON.stringify(ExposedSettings).replace(/\//g, "\\/")}'); - if (typeof(settings.algolia) != "undefined") script. From 5836c029c82e42eaea87f9cc43de8ba19e5ba03a Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Mon, 13 Aug 2018 12:33:31 +0100 Subject: [PATCH 065/104] Show archive heading if archiving and on OL --- services/web/app/views/project/list/modals.pug | 1 + .../coffee/main/project-list/modal-controllers.coffee | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/services/web/app/views/project/list/modals.pug b/services/web/app/views/project/list/modals.pug index aaacf660a6..9ac969b518 100644 --- a/services/web/app/views/project/list/modals.pug +++ b/services/web/app/views/project/list/modals.pug @@ -192,6 +192,7 @@ script(type='text/ng-template', id='deleteProjectsModalTemplate') ng-click="cancel()" ) × h3(ng-if="action == 'delete'") #{translate("delete_projects")} + h3(ng-if="action == 'archive'") #{translate("archive_projects")} h3(ng-if="action == 'leave'") #{translate("leave_projects")} h3(ng-if="action == 'delete-and-leave'") #{translate("delete_and_leave_projects")} .modal-body diff --git a/services/web/public/coffee/main/project-list/modal-controllers.coffee b/services/web/public/coffee/main/project-list/modal-controllers.coffee index 609a16cb77..a8e5d5ec8f 100644 --- a/services/web/public/coffee/main/project-list/modal-controllers.coffee +++ b/services/web/public/coffee/main/project-list/modal-controllers.coffee @@ -102,10 +102,15 @@ define [ $scope.projectsToDelete = projects.filter (project) -> project.accessLevel == "owner" $scope.projectsToLeave = projects.filter (project) -> project.accessLevel != "owner" + if $scope.projectsToLeave.length > 0 and $scope.projectsToDelete.length > 0 $scope.action = "delete-and-leave" else if $scope.projectsToLeave.length == 0 and $scope.projectsToDelete.length > 0 - $scope.action = "delete" + projectsToArchive = $scope.projectsToDelete.filter (project) -> !project.archived + if projectsToArchive.length > 0 and window.ExposedSettings.isOverleaf + $scope.action = "archive" + else + $scope.action = "delete" else $scope.action = "leave" From 009c5c363b1b5cca57cf22b5f054384115fef425 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Mon, 13 Aug 2018 13:52:21 +0100 Subject: [PATCH 066/104] Also fix modal body for archiving projects --- services/web/app/views/project/list/modals.pug | 3 ++- .../coffee/main/project-list/modal-controllers.coffee | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/web/app/views/project/list/modals.pug b/services/web/app/views/project/list/modals.pug index 9ac969b518..3fd8b5b1f6 100644 --- a/services/web/app/views/project/list/modals.pug +++ b/services/web/app/views/project/list/modals.pug @@ -197,7 +197,8 @@ script(type='text/ng-template', id='deleteProjectsModalTemplate') h3(ng-if="action == 'delete-and-leave'") #{translate("delete_and_leave_projects")} .modal-body div(ng-show="projectsToDelete.length > 0") - p #{translate("about_to_delete_projects")} + p(ng-if="action == 'delete'") #{translate("about_to_delete_projects")} + p(ng-if="action == 'archive'") #{translate("about_to_archive_projects")} ul li(ng-repeat="project in projectsToDelete | orderBy:'name'") strong {{project.name}} diff --git a/services/web/public/coffee/main/project-list/modal-controllers.coffee b/services/web/public/coffee/main/project-list/modal-controllers.coffee index a8e5d5ec8f..bec9a8efb4 100644 --- a/services/web/public/coffee/main/project-list/modal-controllers.coffee +++ b/services/web/public/coffee/main/project-list/modal-controllers.coffee @@ -101,13 +101,13 @@ define [ App.controller 'DeleteProjectsModalController', ($scope, $modalInstance, $timeout, projects) -> $scope.projectsToDelete = projects.filter (project) -> project.accessLevel == "owner" $scope.projectsToLeave = projects.filter (project) -> project.accessLevel != "owner" - + $scope.projectsToArchive = projects.filter (project) -> + project.accessLevel == "owner" and !project.archived if $scope.projectsToLeave.length > 0 and $scope.projectsToDelete.length > 0 $scope.action = "delete-and-leave" else if $scope.projectsToLeave.length == 0 and $scope.projectsToDelete.length > 0 - projectsToArchive = $scope.projectsToDelete.filter (project) -> !project.archived - if projectsToArchive.length > 0 and window.ExposedSettings.isOverleaf + if $scope.projectsToArchive.length > 0 and window.ExposedSettings.isOverleaf $scope.action = "archive" else $scope.action = "delete" From bd94e55ce912b773e483ec4ef8fcc2286df61e99 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Mon, 13 Aug 2018 13:59:35 +0100 Subject: [PATCH 067/104] Handle archive and leave action --- services/web/app/views/project/list/modals.pug | 5 +++-- .../public/coffee/main/project-list/modal-controllers.coffee | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/services/web/app/views/project/list/modals.pug b/services/web/app/views/project/list/modals.pug index 3fd8b5b1f6..d3d8e25f23 100644 --- a/services/web/app/views/project/list/modals.pug +++ b/services/web/app/views/project/list/modals.pug @@ -195,10 +195,11 @@ script(type='text/ng-template', id='deleteProjectsModalTemplate') h3(ng-if="action == 'archive'") #{translate("archive_projects")} h3(ng-if="action == 'leave'") #{translate("leave_projects")} h3(ng-if="action == 'delete-and-leave'") #{translate("delete_and_leave_projects")} + h3(ng-if="action == 'archive-and-leave'") #{translate("archive_and_leave_projects")} .modal-body div(ng-show="projectsToDelete.length > 0") - p(ng-if="action == 'delete'") #{translate("about_to_delete_projects")} - p(ng-if="action == 'archive'") #{translate("about_to_archive_projects")} + p(ng-if="action == 'delete' || action == 'delete-and-leave'") #{translate("about_to_delete_projects")} + p(ng-if="action == 'archive' || action == 'archive-and-leave'") #{translate("about_to_archive_projects")} ul li(ng-repeat="project in projectsToDelete | orderBy:'name'") strong {{project.name}} diff --git a/services/web/public/coffee/main/project-list/modal-controllers.coffee b/services/web/public/coffee/main/project-list/modal-controllers.coffee index bec9a8efb4..42a11500cf 100644 --- a/services/web/public/coffee/main/project-list/modal-controllers.coffee +++ b/services/web/public/coffee/main/project-list/modal-controllers.coffee @@ -105,7 +105,10 @@ define [ project.accessLevel == "owner" and !project.archived if $scope.projectsToLeave.length > 0 and $scope.projectsToDelete.length > 0 - $scope.action = "delete-and-leave" + if $scope.projectsToArchive.length > 0 and window.ExposedSettings.isOverleaf + $scope.action = "archive-and-leave" + else + $scope.action = "delete-and-leave" else if $scope.projectsToLeave.length == 0 and $scope.projectsToDelete.length > 0 if $scope.projectsToArchive.length > 0 and window.ExposedSettings.isOverleaf $scope.action = "archive" From 4a65a526cec540290941165f1762dda51f196a57 Mon Sep 17 00:00:00 2001 From: Tim Alby Date: Mon, 13 Aug 2018 17:03:48 +0200 Subject: [PATCH 068/104] require minilist package --- services/web/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/web/package.json b/services/web/package.json index ed0eb06455..ae0a51a3c4 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -98,7 +98,8 @@ "v8-profiler": "^5.2.3", "valid-url": "^1.0.9", "xml2js": "0.2.0", - "yauzl": "^2.8.0" + "yauzl": "^2.8.0", + "minimist": "1.2.0" }, "devDependencies": { "autoprefixer": "^6.6.1", From 663ca275cb02760895ae0fa0aaca4b6fb316cef6 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 13 Aug 2018 17:09:18 +0100 Subject: [PATCH 069/104] Add i18n. --- .../app/views/project/editor/history/previewPanelV2.pug | 4 ++-- .../web/app/views/project/editor/history/toolbarV2.pug | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/web/app/views/project/editor/history/previewPanelV2.pug b/services/web/app/views/project/editor/history/previewPanelV2.pug index 3d7a1ac3df..773d79878f 100644 --- a/services/web/app/views/project/editor/history/previewPanelV2.pug +++ b/services/web/app/views/project/editor/history/previewPanelV2.pug @@ -45,7 +45,7 @@ text="history.diff.text", highlights="history.diff.highlights", read-only="true", - resize-on="layout:main:resize", + resize-on="layout:main:resize,history:toggle", navigate-highlights="true" ) .alert.alert-info(ng-if="history.diff.binary") @@ -70,7 +70,7 @@ font-size="settings.fontSize", text="history.selectedFile.text", read-only="true", - resize-on="layout:main:resize", + resize-on="layout:main:resize,history:toggle", ) .alert.alert-info(ng-if="history.selectedFile.binary") | We're still working on showing image and binary changes, sorry. Stay tuned! diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index 4e4b6f7ae3..aa459e0b32 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -11,7 +11,7 @@ time.history-toolbar-time {{ history.selection.updates[0].meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }} span.history-toolbar-selected-version( ng-show="!history.loadingFileTree && history.showOnlyLabels && history.selection.label" - ) Browsing project version labelled + ) #{translate("browsing_project_labelled")}  span.history-toolbar-selected-label "{{ history.selection.label.comment }}" div.history-toolbar-actions button.history-toolbar-btn( @@ -31,9 +31,9 @@ .history-toolbar-entries-list toggle-switch( ng-model="history.showOnlyLabels" - label-true="Labels" - label-false="All history" - description="Show all of the project history or only labelled versions." + label-true=translate("history_view_labels") + label-false=translate("history_view_all") + description=translate("history_view_a11y_description") ) script(type="text/ng-template", id="historyV2AddLabelModalTemplate") From 6b31268bfa65096b82dd17bd0b28f9c6f451a4dd Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Tue, 14 Aug 2018 15:27:07 +0100 Subject: [PATCH 070/104] Scroll to selected history update when coming back from the labels view. --- .../history/components/historyEntriesList.coffee | 16 ++++++++++++++++ .../ide/history/components/historyEntry.coffee | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee index 934ca304fe..de4e4f1b92 100644 --- a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee @@ -3,6 +3,22 @@ define [ ], (App) -> historyEntriesListController = ($scope, $element, $attrs) -> ctrl = @ + ctrl.$entryListViewportEl = null + _isEntryElVisible = ($entryEl) -> + entryElTop = $entryEl.offset().top + entryElBottom = entryElTop + $entryEl.outerHeight() + entryListViewportElTop = ctrl.$entryListViewportEl.offset().top + entryListViewportElBottom = entryListViewportElTop + ctrl.$entryListViewportEl.height() + return entryElTop >= entryListViewportElTop and entryElBottom <= entryListViewportElBottom; + _getScrollTopPosForEntry = ($entryEl) -> + halfViewportElHeight = ctrl.$entryListViewportEl.height() / 2 + return $entryEl.offset().top - halfViewportElHeight + ctrl.onEntryLinked = (entry, $entryEl) -> + if entry.selectedTo and entry.selectedFrom and !_isEntryElVisible $entryEl + $scope.$applyAsync () -> + ctrl.$entryListViewportEl.scrollTop _getScrollTopPosForEntry $entryEl + ctrl.$onInit = () -> + ctrl.$entryListViewportEl = $element.find "> .history-entries" return App.component "historyEntriesList", { diff --git a/services/web/public/coffee/ide/history/components/historyEntry.coffee b/services/web/public/coffee/ide/history/components/historyEntry.coffee index 8af726b509..6d5eed53d5 100644 --- a/services/web/public/coffee/ide/history/components/historyEntry.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntry.coffee @@ -26,6 +26,8 @@ define [ color : "#FFF" else color: "hsl(#{ hue }, 70%, 50%)" + ctrl.$onInit = () -> + ctrl.historyEntriesList.onEntryLinked ctrl.entry, $element.find "> .history-entry" return App.component "historyEntry", { @@ -35,6 +37,8 @@ define [ users: "<" onSelect: "&" onLabelDelete: "&" + require: + historyEntriesList: '^historyEntriesList' controller: historyEntryController templateUrl: "historyEntryTpl" } \ No newline at end of file From 2c9977d26ed642453246da96e8ada574e30189ab Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Thu, 9 Aug 2018 11:02:48 -0500 Subject: [PATCH 071/104] Add analytics to link mixins --- services/web/app/views/_mixins_links.pug | 63 +++++++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/services/web/app/views/_mixins_links.pug b/services/web/app/views/_mixins_links.pug index 18b208270c..bad56ffc25 100644 --- a/services/web/app/views/_mixins_links.pug +++ b/services/web/app/views/_mixins_links.pug @@ -1,9 +1,19 @@ -mixin linkAdvisors(linkText, linkClass, tracked) +mixin linkAdvisors(linkText, linkClass, track) //- To Do: verify path - //- To Do: track + - var gaCategory = track && track.category ? track.category : 'All' + - var gaAction = track && track.action ? track.action : null + - var gaLabel = track && track.label ? track.label : null + - var mb = track && track.mb ? 'true' : null + - var mbSegmentation = track && track.segmentation ? track.segmentation : null + - var trigger = track && track.trigger ? track.trigger : null a(href="/advisors" class=linkClass ? linkClass : '' - eventTrackingGa=track ? 'advisors' : null + event-tracking-ga=gaCategory + event-tracking=gaAction + event-tracking-label=gaLabel + event-tracking-trigger=trigger + event-tracking-mb=mb + event-segmentation=mbSegmentation ) | #{linkText ? linkText : 'advisor programme'} @@ -32,8 +42,23 @@ mixin linkEmail(linkText, linkClass, email) a(href="mailto:#{email ? email : 'contact'}@#{emailDomain}" class=linkClass ? linkClass : '') | #{linkText ? linkText : 'email'} -mixin linkInvite(linkText, linkClass) - a(href="/user/bonus" class=linkClass ? linkClass : '') +mixin linkInvite(linkText, linkClass, track) + - var gaCategory = track && track.category ? track.category : 'All' + - var gaAction = track && track.action ? track.action : null + - var gaLabel = track && track.label ? track.label : null + - var mb = track && track.mb ? 'true' : null + - var mbSegmentation = track && track.segmentation ? track.segmentation : null + - var trigger = track && track.trigger ? track.trigger : null + + a(href="/user/bonus" + class=linkClass ? linkClass : '' + event-tracking-ga=gaCategory + event-tracking=gaAction + event-tracking-label=gaLabel + event-tracking-trigger=trigger + event-tracking-mb=mb + event-segmentation=mbSegmentation + ) | #{linkText ? linkText : 'invite your friends'} mixin linkPlansAndPricing(linkText, linkClass) @@ -42,10 +67,21 @@ mixin linkPlansAndPricing(linkText, linkClass) | #{linkText ? linkText : 'plans and pricing'} mixin linkPrintNewTab(linkText, linkClass, icon, track) - //- To Do: track + - var gaCategory = track && track.category ? track.category : null + - var gaAction = track && track.action ? track.action : null + - var gaLabel = track && track.label ? track.label : null + - var mb = track && track.mb ? 'true' : null + - var mbSegmentation = track && track.segmentation ? track.segmentation : null + - var trigger = track && track.trigger ? track.trigger : null + a(href='?media=print' class=linkClass ? linkClass : '' - eventTrackingGa=track ? 'print' : null + event-tracking-ga=gaCategory + event-tracking=gaAction + event-tracking-label=gaLabel + event-tracking-trigger=trigger + event-tracking-mb=mb + event-segmentation=mbSegmentation target="_BLANK" ) if icon @@ -63,8 +99,19 @@ mixin linkSignUp(linkText, linkClass) mixin linkTweet(linkText, linkClass, tweetText, track) //- twitter-share-button is required by twitter - //- To Do: track + - var gaCategory = track && track.category ? track.category : 'All' + - var gaAction = track && track.action ? track.action : null + - var gaLabel = track && track.label ? track.label : null + - var mb = track && track.mb ? 'true' : null + - var mbSegmentation = track && track.segmentation ? track.segmentation : null + - var trigger = track && track.trigger ? track.trigger : null a(class="twitter-share-button " + linkClass + event-tracking-ga=gaCategory + event-tracking=gaAction + event-tracking-label=gaLabel + event-tracking-trigger=trigger + event-tracking-mb=mb + event-segmentation=mbSegmentation href="https://twitter.com/intent/tweet?text=" + tweetText target="_BLANK" ) #{linkText ? linkText : 'tweet'} From fc0b9df14ccc30c7fb7ce243fbb1e63bb101d4c2 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Tue, 7 Aug 2018 14:54:23 +0100 Subject: [PATCH 072/104] Add feature flag for redirecting SL to v2 --- services/web/app/coffee/infrastructure/Features.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/app/coffee/infrastructure/Features.coffee b/services/web/app/coffee/infrastructure/Features.coffee index ffc60604e6..496bbf1e39 100644 --- a/services/web/app/coffee/infrastructure/Features.coffee +++ b/services/web/app/coffee/infrastructure/Features.coffee @@ -25,5 +25,7 @@ module.exports = Features = when 'rich-text' isEnabled = true # Switch to false to disable Settings.overleaf? and isEnabled + when 'redirect-sl' + return Settings.createV1AccountOnLogin else throw new Error("unknown feature: #{feature}") From 985344c0d74edfb3c138525213d336e670048236 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Wed, 8 Aug 2018 15:24:20 +0100 Subject: [PATCH 073/104] Use redirect setting instead of re-using setting that coincidentally would be flipped on the same day --- services/web/app/coffee/infrastructure/Features.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/coffee/infrastructure/Features.coffee b/services/web/app/coffee/infrastructure/Features.coffee index 496bbf1e39..43dadcda2d 100644 --- a/services/web/app/coffee/infrastructure/Features.coffee +++ b/services/web/app/coffee/infrastructure/Features.coffee @@ -26,6 +26,6 @@ module.exports = Features = isEnabled = true # Switch to false to disable Settings.overleaf? and isEnabled when 'redirect-sl' - return Settings.createV1AccountOnLogin + return Settings.redirectToV2? else throw new Error("unknown feature: #{feature}") From af6e7758d38b6edff684674075bbad519446d39b Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 15 Aug 2018 15:26:22 +0100 Subject: [PATCH 074/104] configure backend group name via config files --- services/web/app/coffee/Features/Compile/ClsiManager.coffee | 4 ++-- .../web/app/coffee/Features/Compile/CompileController.coffee | 2 +- services/web/app/coffee/router.coffee | 2 +- services/web/config/settings.defaults.coffee | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/web/app/coffee/Features/Compile/ClsiManager.coffee b/services/web/app/coffee/Features/Compile/ClsiManager.coffee index 8d5c5b2baa..b0f7a01fa1 100755 --- a/services/web/app/coffee/Features/Compile/ClsiManager.coffee +++ b/services/web/app/coffee/Features/Compile/ClsiManager.coffee @@ -7,8 +7,8 @@ ProjectGetter = require("../Project/ProjectGetter") ProjectEntityHandler = require("../Project/ProjectEntityHandler") logger = require "logger-sharelatex" Url = require("url") -ClsiCookieManager = require("./ClsiCookieManager")() -NewBackendCloudClsiCookieManager = require("./ClsiCookieManager")("newBackendcloud") +ClsiCookieManager = require("./ClsiCookieManager")(Settings.apis.clsi?.backendGroupName) +NewBackendCloudClsiCookieManager = require("./ClsiCookieManager")(Settings.apis.clsi_new?.backendGroupName) ClsiStateManager = require("./ClsiStateManager") _ = require("underscore") async = require("async") diff --git a/services/web/app/coffee/Features/Compile/CompileController.coffee b/services/web/app/coffee/Features/Compile/CompileController.coffee index d79eb5d389..8a3f92ac66 100755 --- a/services/web/app/coffee/Features/Compile/CompileController.coffee +++ b/services/web/app/coffee/Features/Compile/CompileController.coffee @@ -9,7 +9,7 @@ Settings = require "settings-sharelatex" AuthenticationController = require "../Authentication/AuthenticationController" UserGetter = require "../User/UserGetter" RateLimiter = require("../../infrastructure/RateLimiter") -ClsiCookieManager = require("./ClsiCookieManager")() +ClsiCookieManager = require("./ClsiCookieManager")(Settings.apis.clsi?.backendGroupName) Path = require("path") module.exports = CompileController = diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index f48882a634..b590e58f5d 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -22,7 +22,7 @@ UserPagesController = require('./Features/User/UserPagesController') DocumentController = require('./Features/Documents/DocumentController') CompileManager = require("./Features/Compile/CompileManager") CompileController = require("./Features/Compile/CompileController") -ClsiCookieManager = require("./Features/Compile/ClsiCookieManager")() +ClsiCookieManager = require("./Features/Compile/ClsiCookieManager")(Settings.apis.clsi?.backendGroupName) HealthCheckController = require("./Features/HealthCheck/HealthCheckController") ProjectDownloadsController = require "./Features/Downloads/ProjectDownloadsController" FileStoreController = require("./Features/FileStore/FileStoreController") diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 1306ce91c2..802183357c 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -135,6 +135,7 @@ module.exports = settings = url: "http://#{process.env['FILESTORE_HOST'] or 'localhost'}:3009" clsi: url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013" + backendGroupName: undefined templates: url: "http://#{process.env['TEMPLATES_HOST'] or 'localhost'}:3007" githubSync: From 23bb866988ac026c2c61595b8dfcffdcfe534b3c Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 16 Aug 2018 11:26:34 +0100 Subject: [PATCH 075/104] Show an error message when history fails to load updates. --- .../views/project/editor/history/entriesListV2.pug | 4 ++-- .../views/project/editor/history/previewPanelV2.pug | 11 +++++++++++ .../app/views/project/editor/history/toolbarV2.pug | 12 ++++++++---- .../coffee/ide/history/HistoryV2Manager.coffee | 6 ++++++ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index 21c2438742..5e4c4b66c6 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -3,7 +3,7 @@ aside.change-list( ng-controller="HistoryV2ListController" ) history-entries-list( - ng-if="!history.showOnlyLabels" + ng-if="!history.showOnlyLabels && !history.error" entries="history.updates" current-user="user" users="projectUsers" @@ -15,7 +15,7 @@ aside.change-list( on-label-delete="handleLabelDelete(label)" ) history-labels-list( - ng-if="history.showOnlyLabels" + ng-if="history.showOnlyLabels && !history.error" labels="history.labels" current-user="user" users="projectUsers" diff --git a/services/web/app/views/project/editor/history/previewPanelV2.pug b/services/web/app/views/project/editor/history/previewPanelV2.pug index 773d79878f..bd814c2da4 100644 --- a/services/web/app/views/project/editor/history/previewPanelV2.pug +++ b/services/web/app/views/project/editor/history/previewPanelV2.pug @@ -77,5 +77,16 @@ .loading-panel(ng-show="history.selectedFile.loading") i.fa.fa-spin.fa-refresh |   #{translate("loading")}... + .error-panel(ng-show="history.error") + .alert.alert-danger + | #{translate("generic_history_error")} + a( + ng-href="mailto:#{settings.adminEmail}?Subject=Error%20loading%20history%20for%project%20{{ project_id }}" + ) #{settings.adminEmail} + .clearfix + a.alert-link-as-btn.pull-right( + href + ng-click="toggleHistory()" + ) #{translate("back_to_editor")} .error-panel(ng-show="history.selectedFile.error") .alert.alert-danger #{translate("generic_something_went_wrong")} diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index aa459e0b32..a683facba4 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -6,14 +6,16 @@ i.fa.fa-spin.fa-refresh |    #{translate("loading")}... span.history-toolbar-selected-version( - ng-show="!history.loadingFileTree && !history.showOnlyLabels" + ng-show="!history.loadingFileTree && !history.showOnlyLabels && !history.error" ) #{translate("browsing_project_as_of")}  time.history-toolbar-time {{ history.selection.updates[0].meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }} span.history-toolbar-selected-version( - ng-show="!history.loadingFileTree && history.showOnlyLabels && history.selection.label" + ng-show="!history.loadingFileTree && history.showOnlyLabels && history.selection.label && !history.error" ) #{translate("browsing_project_labelled")}  span.history-toolbar-selected-label "{{ history.selection.label.comment }}" - div.history-toolbar-actions + div.history-toolbar-actions( + ng-if="!history.error" + ) button.history-toolbar-btn( ng-click="showAddLabelDialog();" ng-if="!history.showOnlyLabels" @@ -28,7 +30,9 @@ i.fa.fa-exchange |  #{translate("compare_to_another_version")} - .history-toolbar-entries-list + .history-toolbar-entries-list( + ng-if="!history.error" + ) toggle-switch( ng-model="history.showOnlyLabels" label-true=translate("history_view_labels") diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index aa4ba09d62..3f231c046b 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -92,6 +92,7 @@ define [ toV: null } } + error: null showOnlyLabels: false labels: null files: [] @@ -234,6 +235,11 @@ define [ if !updatesData.nextBeforeTimestamp? @$scope.history.atEnd = true @$scope.history.loading = false + .catch (error) => + { status, statusText } = error + @$scope.history.error = { status, statusText } + @$scope.history.loading = false + @$scope.history.loadingFileTree = false _sortLabelsByVersionAndDate: (labels) -> @ide.$filter("orderBy")(labels, [ '-version', '-created_at' ]) From 652be9efe693622851ca6adad47ec41c20f03c60 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 16 Aug 2018 11:41:19 +0100 Subject: [PATCH 076/104] Slightly adapt the markup for the generic history error. --- .../views/project/editor/history/previewPanelV2.pug | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/services/web/app/views/project/editor/history/previewPanelV2.pug b/services/web/app/views/project/editor/history/previewPanelV2.pug index bd814c2da4..e75af8db90 100644 --- a/services/web/app/views/project/editor/history/previewPanelV2.pug +++ b/services/web/app/views/project/editor/history/previewPanelV2.pug @@ -79,11 +79,12 @@ |   #{translate("loading")}... .error-panel(ng-show="history.error") .alert.alert-danger - | #{translate("generic_history_error")} - a( - ng-href="mailto:#{settings.adminEmail}?Subject=Error%20loading%20history%20for%project%20{{ project_id }}" - ) #{settings.adminEmail} - .clearfix + p + | #{translate("generic_history_error")} + a( + ng-href="mailto:#{settings.adminEmail}?Subject=Error%20loading%20history%20for%project%20{{ project_id }}" + ) #{settings.adminEmail} + p.clearfix a.alert-link-as-btn.pull-right( href ng-click="toggleHistory()" From 713a39171ae4334ec6128bb620cccc752f509edb Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 16 Aug 2018 13:44:48 +0100 Subject: [PATCH 077/104] Fix history frontend tests. --- .../coffee/ide/history/HistoryV2ManagerTests.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee b/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee index 725befb721..2fc5c5b3b6 100644 --- a/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee +++ b/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee @@ -15,14 +15,17 @@ define ['ide/history/HistoryV2Manager'], (HistoryV2Manager) -> nextBeforeTimestamp: null atEnd: false selection: { + label: null updates: [] - pathname: null docs: {} + pathname: null range: { fromV: null toV: null } } + error: null + showOnlyLabels: false labels: null diff: null files: [] From 55597b9279fe2574d811f339dec4c62ca1460cfc Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Thu, 2 Aug 2018 16:19:23 +0100 Subject: [PATCH 078/104] inform v1 when confirming affiliation emails --- .../app/coffee/Features/Institutions/InstitutionsAPI.coffee | 4 ++-- services/web/app/coffee/Features/User/UserUpdater.coffee | 2 +- .../test/unit/coffee/Institutions/InstitutionsAPITests.coffee | 4 +++- services/web/test/unit/coffee/User/UserUpdaterTests.coffee | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index 8ce39d68e3..c285ba89ff 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -25,11 +25,11 @@ module.exports = InstitutionsAPI = callback = affiliationOptions affiliationOptions = {} - { university, department, role } = affiliationOptions + { university, department, role, confirmed } = affiliationOptions makeAffiliationRequest { method: 'POST' path: "/api/v2/users/#{userId.toString()}/affiliations" - body: { email, university, department, role } + body: { email, university, department, role, confirmed } defaultErrorMessage: "Couldn't create affiliation" }, callback diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index 9ee81c1ca3..119bf07285 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -166,7 +166,7 @@ module.exports = UserUpdater = email = EmailHelper.parseEmail(email) return callback(new Error('invalid email')) if !email? logger.log {userId, email}, 'confirming user email' - addAffiliation userId, email, (error) => + addAffiliation userId, email, { confirmed: true }, (error) => if error? logger.err error: error, 'problem adding affiliation while confirming email' return callback(error) diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee index 7500e46e40..79859aa93d 100644 --- a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee +++ b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee @@ -74,6 +74,7 @@ describe "InstitutionsAPI", -> university: { id: 1 } role: 'Prof' department: 'Math' + confirmed: true @InstitutionsAPI.addAffiliation @stubbedUser._id, @newEmail, affiliationOptions, (err)=> should.not.exist(err) @request.calledOnce.should.equal true @@ -83,11 +84,12 @@ describe "InstitutionsAPI", -> requestOptions.method.should.equal 'POST' body = requestOptions.body - Object.keys(body).length.should.equal 4 + Object.keys(body).length.should.equal 5 body.email.should.equal @newEmail body.university.should.equal affiliationOptions.university body.department.should.equal affiliationOptions.department body.role.should.equal affiliationOptions.role + body.confirmed.should.equal affiliationOptions.confirmed done() it 'handle error', (done)-> diff --git a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee index 17f691edba..5ce5e1fc6a 100644 --- a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee +++ b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee @@ -241,7 +241,7 @@ describe "UserUpdater", -> @UserUpdater.confirmEmail @stubbedUser._id, @newEmail, (err)=> should.not.exist(err) @addAffiliation.calledOnce.should.equal true - sinon.assert.calledWith(@addAffiliation, @stubbedUser._id, @newEmail) + sinon.assert.calledWith(@addAffiliation, @stubbedUser._id, @newEmail, { confirmed: true } ) done() it 'handle error', (done)-> @@ -264,7 +264,7 @@ describe "UserUpdater", -> done() it 'handle affiliation error', (done)-> - @addAffiliation.callsArgWith(2, new Error('nope')) + @addAffiliation.callsArgWith(3, new Error('nope')) @UserUpdater.confirmEmail @stubbedUser._id, @newEmail, (err)=> should.exist(err) @UserUpdater.updateUser.called.should.equal false From 96ffeef73d9ea4cecda6fc51770e2abc38f04d3b Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Thu, 9 Aug 2018 08:20:34 +0100 Subject: [PATCH 079/104] send fixed confirmed date to v1 for affiliations --- .../app/coffee/Features/Institutions/InstitutionsAPI.coffee | 4 ++-- services/web/app/coffee/Features/User/UserUpdater.coffee | 2 +- .../test/unit/coffee/Institutions/InstitutionsAPITests.coffee | 4 ++-- services/web/test/unit/coffee/User/UserUpdaterTests.coffee | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index c285ba89ff..0d30b925aa 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -25,11 +25,11 @@ module.exports = InstitutionsAPI = callback = affiliationOptions affiliationOptions = {} - { university, department, role, confirmed } = affiliationOptions + { university, department, role, confirmedAt } = affiliationOptions makeAffiliationRequest { method: 'POST' path: "/api/v2/users/#{userId.toString()}/affiliations" - body: { email, university, department, role, confirmed } + body: { email, university, department, role, confirmedAt } defaultErrorMessage: "Couldn't create affiliation" }, callback diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index 119bf07285..3abffdf444 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -166,7 +166,7 @@ module.exports = UserUpdater = email = EmailHelper.parseEmail(email) return callback(new Error('invalid email')) if !email? logger.log {userId, email}, 'confirming user email' - addAffiliation userId, email, { confirmed: true }, (error) => + addAffiliation userId, email, {confirmedAt: confirmedAt}, (error) => if error? logger.err error: error, 'problem adding affiliation while confirming email' return callback(error) diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee index 79859aa93d..04f0b61e8e 100644 --- a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee +++ b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee @@ -74,7 +74,7 @@ describe "InstitutionsAPI", -> university: { id: 1 } role: 'Prof' department: 'Math' - confirmed: true + confirmedAt: new Date() @InstitutionsAPI.addAffiliation @stubbedUser._id, @newEmail, affiliationOptions, (err)=> should.not.exist(err) @request.calledOnce.should.equal true @@ -89,7 +89,7 @@ describe "InstitutionsAPI", -> body.university.should.equal affiliationOptions.university body.department.should.equal affiliationOptions.department body.role.should.equal affiliationOptions.role - body.confirmed.should.equal affiliationOptions.confirmed + body.confirmedAt.should.equal affiliationOptions.confirmedAt done() it 'handle error', (done)-> diff --git a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee index 5ce5e1fc6a..3be7733153 100644 --- a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee +++ b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee @@ -241,7 +241,7 @@ describe "UserUpdater", -> @UserUpdater.confirmEmail @stubbedUser._id, @newEmail, (err)=> should.not.exist(err) @addAffiliation.calledOnce.should.equal true - sinon.assert.calledWith(@addAffiliation, @stubbedUser._id, @newEmail, { confirmed: true } ) + sinon.assert.calledWith(@addAffiliation, @stubbedUser._id, @newEmail, { confirmedAt: new Date() } ) done() it 'handle error', (done)-> From 3b31125a62b4095016a574dd8dfade8da60a3a4b Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Thu, 16 Aug 2018 14:11:43 +0100 Subject: [PATCH 080/104] Add message about deleting account affecting v2 Only show on SL and if the user has linked accounts --- services/web/app/views/user/settings.pug | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/web/app/views/user/settings.pug b/services/web/app/views/user/settings.pug index e4da83c1db..d231047d8d 100644 --- a/services/web/app/views/user/settings.pug +++ b/services/web/app/views/user/settings.pug @@ -167,7 +167,12 @@ block content ) i.fa.fa-check | #{translate("unsubscribed")} - + + if !settings.overleaf && user.overleaf + p + | Please note: If you have linked your account with Overleaf + | v2, then deleting your ShareLaTeX account will also delete + | account and all of it's associated projects and data. p #{translate("need_to_leave")} a(href, ng-click="deleteAccount()") #{translate("delete_your_account")} From c33192b852f24579dc60cd4fac27f4af2300aad2 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Mon, 16 Jul 2018 16:18:08 +0100 Subject: [PATCH 081/104] download log files using buildPdfDownloadUrl --- .../ide/pdf/controllers/PdfController.coffee | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index 389631c0a7..c717ef6b82 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -224,6 +224,12 @@ define [ _csrf: window.csrfToken }, {params: params} + buildPdfDownloadUrl = (pdfDownloadDomain, path)-> + if pdfDownloadDomain? + return "#{pdfDownloadDomain}#{path}" + else + return path + parseCompileResponse = (response) -> # keep last url @@ -244,11 +250,7 @@ define [ $scope.pdf.compileInProgress = false $scope.pdf.autoCompileDisabled = false - buildPdfDownloadUrl = (path)-> - if pdfDownloadDomain? - return "#{pdfDownloadDomain}#{path}" - else - return path + # make a cache to look up files by name fileByPath = {} if response?.outputFiles? @@ -267,24 +269,24 @@ define [ if response.status == "timedout" $scope.pdf.view = 'errors' $scope.pdf.timedout = true - fetchLogs(fileByPath) + fetchLogs(fileByPath, {pdfDownloadDomain:pdfDownloadDomain}) else if response.status == "terminated" $scope.pdf.view = 'errors' $scope.pdf.compileTerminated = true - fetchLogs(fileByPath) + fetchLogs(fileByPath, {pdfDownloadDomain:pdfDownloadDomain}) else if response.status in ["validation-fail", "validation-pass"] $scope.pdf.view = 'pdf' - $scope.pdf.url = buildPdfDownloadUrl last_pdf_url + $scope.pdf.url = buildPdfDownloadUrl pdfDownloadDomain, last_pdf_url $scope.shouldShowLogs = true $scope.pdf.failedCheck = true if response.status is "validation-fail" event_tracking.sendMB "syntax-check-#{response.status}" - fetchLogs(fileByPath, { validation: true }) + fetchLogs(fileByPath, { validation: true, pdfDownloadDomain:pdfDownloadDomain}) else if response.status == "exited" $scope.pdf.view = 'pdf' $scope.pdf.compileExited = true - $scope.pdf.url = buildPdfDownloadUrl last_pdf_url + $scope.pdf.url = buildPdfDownloadUrl pdfDownloadDomain, last_pdf_url $scope.shouldShowLogs = true - fetchLogs(fileByPath) + fetchLogs(fileByPath, {pdfDownloadDomain:pdfDownloadDomain}) else if response.status == "autocompile-backoff" if $scope.pdf.isAutoCompileOnLoad # initial autocompile $scope.pdf.view = 'uncompiled' @@ -300,7 +302,7 @@ define [ $scope.pdf.view = 'errors' $scope.pdf.failure = true $scope.shouldShowLogs = true - fetchLogs(fileByPath) + fetchLogs(fileByPath, {pdfDownloadDomain:pdfDownloadDomain}) else if response.status == 'clsi-maintenance' $scope.pdf.view = 'errors' $scope.pdf.clsiMaintenance = true @@ -320,12 +322,12 @@ define [ # define the base url. if the pdf file has a build number, pass it to the clsi in the url if fileByPath['output.pdf']?.url? - $scope.pdf.url = buildPdfDownloadUrl fileByPath['output.pdf'].url + $scope.pdf.url = buildPdfDownloadUrl pdfDownloadDomain, fileByPath['output.pdf'].url else if fileByPath['output.pdf']?.build? build = fileByPath['output.pdf'].build - $scope.pdf.url = buildPdfDownloadUrl "/project/#{$scope.project_id}/build/#{build}/output/output.pdf" + $scope.pdf.url = buildPdfDownloadUrl pdfDownloadDomain, "/project/#{$scope.project_id}/build/#{build}/output/output.pdf" else - $scope.pdf.url = buildPdfDownloadUrl "/project/#{$scope.project_id}/output/output.pdf" + $scope.pdf.url = buildPdfDownloadUrl pdfDownloadDomain, "/project/#{$scope.project_id}/output/output.pdf" # check if we need to bust cache (build id is unique so don't need it in that case) if not fileByPath['output.pdf']?.build? qs.cache_bust = "#{Date.now()}" @@ -335,7 +337,7 @@ define [ qs.popupDownload = true $scope.pdf.downloadUrl = "/project/#{$scope.project_id}/output/output.pdf" + createQueryString(qs) - fetchLogs(fileByPath) + fetchLogs(fileByPath, {pdfDownloadDomain:pdfDownloadDomain}) IGNORE_FILES = ["output.fls", "output.fdb_latexmk"] $scope.pdf.outputFiles = [] @@ -376,11 +378,11 @@ define [ compileGroup:ide.compileGroup clsiserverid:ide.clsiServerId if file?.url? # FIXME clean this up when we have file.urls out consistently - opts.url = file.url + opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, file.url else if file?.build? - opts.url = "/project/#{$scope.project_id}/build/#{file.build}/output/#{name}" + opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, "/project/#{$scope.project_id}/build/#{file.build}/output/#{name}" else - opts.url = "/project/#{$scope.project_id}/output/#{name}" + opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, "/project/#{$scope.project_id}/output/#{name}" # check if we need to bust cache (build id is unique so don't need it in that case) if not file?.build? opts.params.cache_bust = "#{Date.now()}" From dfc25a093b3959e3494908b49675b3bce6aeccfc Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 16 Aug 2018 16:28:02 +0100 Subject: [PATCH 082/104] Fix review icon for Overleaf. --- .../web/public/img/review-icon-sprite-ol.png | Bin 0 -> 765 bytes .../public/img/review-icon-sprite-ol@2x.png | Bin 0 -> 1082 bytes .../stylesheets/app/editor/review-panel.less | 17 ++++++++++++----- 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 services/web/public/img/review-icon-sprite-ol.png create mode 100644 services/web/public/img/review-icon-sprite-ol@2x.png diff --git a/services/web/public/img/review-icon-sprite-ol.png b/services/web/public/img/review-icon-sprite-ol.png new file mode 100644 index 0000000000000000000000000000000000000000..1e5c7192e4d649f6e1cff67cbc31dfffaca2c6ea GIT binary patch literal 765 zcmVeV^tcYf!5pY!|oebOAq2~0(q7MP-v3jE(nx{o%~QG!Lt zLkPooi&yBy`+pg@5IeCA%M-M-pdGl3n;7>KI4#C<%EDpPW47wL@mybMfijVgVw56R z?H=GLI+I4Wz%Dts>sstRmEw(6E(Jqi=`l>67d#3(8!@Re1r?yXQ=qps~eYvVl|oz)9b zgf6uEToKtg?Yf??<2Q`q6Fw)~Kyq~aVDrsqV4L|%ab4FdJogJwgR9t&V60hPf^qa} zk|pcn#TY>w!s^;q-D}XhtPCQebDOxC-Z4%`rrFbyzYgu1B2ME zy3Odo0>Q vFWH%0}WF zc!BXTe@Q0s)|&?(o1e)f-+XUo-n`kREX#t!WJiKSW}dJo>} z!7U{BruaKYEF@u0DnjsMX8GJ6HozD$ZG!Mp$yk~z*a7qeHLDs|d;}QOsy-)N0Bt~= z)o(GrD{ze>3ikm!7UezyKH>2@?nT1A3;4SRShuKa3$WX?!XaRyQDY8x2-pN1E7002 z_{=t7rm25|-{3@H8?ve5LtX){1C||q46p08;$5jL(q+hg#*YhX!ZmzHEu@Q*ec(`0 z9hUK3DY|=(4}kM^5S}Q=1`T6PaxHRAxYsaQMj*T2M&$R;xg?x6NOnikPidKM z+t|F6=d}!kzci?>NZ7P;8ZA-+pikWPatT-E!o+|CF|_W}h7o$DhAAkMjJl#Fno&AI zWSS7PU1VCKmAMWfY5h#n(5Z&7t+j2Zr7=0b=+Koz;;vhm^$19xwH>MXvT*~@73#?? zv32RTq_s93sXHs6BGcSU6%iR-Vn#L!&m}4k3e1fvqp6^-!LxJ&*M>yV{5pN7)hNA1 z&d&m#RwNt~a$`wD(*Q(fV@D2sR@0EEy?N zA~4i>i7uxlo7;FGvJH4CDSr|hhAMThghzbC_wVC3tP7Ff)G#n!#p7RqD}cAQIW4Bi zWj&FUzmy$3dsH-Q$qx8SqqIIhI6m)_Li^ zBnq1wo-wU+DPl$NFeDjFT^*?;iu((;TP?fTx{08|DZmN9cS(7gzwQbiBa3+ls7R0W z$GN1BhKhb15$_qf*mTB2!k(}v>wrg*!~g&Q07*qoM6N<$g7n$+ A0ssI2 literal 0 HcmV?d00001 diff --git a/services/web/public/stylesheets/app/editor/review-panel.less b/services/web/public/stylesheets/app/editor/review-panel.less index 800a25618f..a7e7d3fdbd 100644 --- a/services/web/public/stylesheets/app/editor/review-panel.less +++ b/services/web/public/stylesheets/app/editor/review-panel.less @@ -922,7 +922,7 @@ } } -.review-icon { +.review-icon when (@is-overleaf = false) { display: inline-block; background: url('/img/review-icon-sprite.png') top/30px no-repeat; width: 30px; @@ -945,10 +945,17 @@ } } -.review-icon when (@is-overleaf) { - background-position-y: -60px; - .toolbar .btn-full-height:hover & { - background-position-y: -60px; +.review-icon when (@is-overleaf = true) { + display: inline-block; + background: url('/img/review-icon-sprite-ol.png') top/30px no-repeat; + width: 30px; + + &::before { + content: '\00a0'; // Non-breakable space. A non-breakable character here makes this icon work like font-awesome. + } + + @media (min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { + background-image: url('/img/review-icon-sprite-ol@2x.png'); } } From 9bc3fa2df0538a143f320d176a8af354317fe1d0 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Fri, 17 Aug 2018 12:04:05 +0100 Subject: [PATCH 083/104] Pass req to preDoPassportLogin module hook --- .../Features/Authentication/AuthenticationController.coffee | 2 +- .../Authentication/AuthenticationControllerTests.coffee | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee index 951506891f..d7e9ed4bc6 100644 --- a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee +++ b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee @@ -84,7 +84,7 @@ module.exports = AuthenticationController = doPassportLogin: (req, username, password, done) -> email = username.toLowerCase() Modules = require "../../infrastructure/Modules" - Modules.hooks.fire 'preDoPassportLogin', email, (err, infoList) -> + Modules.hooks.fire 'preDoPassportLogin', req, email, (err, infoList) -> return next(err) if err? info = infoList.find((i) => i?) if info? diff --git a/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee b/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee index ae8fedc9c8..24af9971d2 100644 --- a/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee +++ b/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee @@ -214,7 +214,7 @@ describe "AuthenticationController", -> beforeEach -> @AuthenticationController._recordFailedLogin = sinon.stub() @AuthenticationController._recordSuccessfulLogin = sinon.stub() - @Modules.hooks.fire = sinon.stub().callsArgWith(2, null, []) + @Modules.hooks.fire = sinon.stub().callsArgWith(3, null, []) # @AuthenticationController.establishUserSession = sinon.stub().callsArg(2) @req.body = email: @email @@ -225,7 +225,7 @@ describe "AuthenticationController", -> describe "when the preDoPassportLogin hooks produce an info object", -> beforeEach -> - @Modules.hooks.fire = sinon.stub().callsArgWith(2, null, [null, {redir: '/somewhere'}, null]) + @Modules.hooks.fire = sinon.stub().callsArgWith(3, null, [null, {redir: '/somewhere'}, null]) it "should stop early and call done with this info object", (done) -> @AuthenticationController.doPassportLogin(@req, @req.body.email, @req.body.password, @cb) From 356e31caecd1173f1eba502d12f9007a0ef525d9 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Wed, 15 Aug 2018 13:32:26 -0500 Subject: [PATCH 084/104] Set variant when sixpack request successful --- services/web/public/coffee/main/plans.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/web/public/coffee/main/plans.coffee b/services/web/public/coffee/main/plans.coffee index 1637d79374..258f84b62c 100644 --- a/services/web/public/coffee/main/plans.coffee +++ b/services/web/public/coffee/main/plans.coffee @@ -152,8 +152,9 @@ define [ if $scope.shouldABTestPlans sixpack.participate 'plans-details', ['default', 'more-details'], (chosenVariation, rawResponse)-> - $scope.plansVariant = chosenVariation - event_tracking.send 'subscription-funnel', 'plans-page-loaded', chosenVariation + if rawResponse?.status != 'failed' + $scope.plansVariant = chosenVariation + event_tracking.send 'subscription-funnel', 'plans-page-loaded', chosenVariation $scope.showPlans = true From 27823d3e06f56cc0a14f3a5d8c4acc1d490b1141 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 17 Aug 2018 15:31:15 +0100 Subject: [PATCH 085/104] Show history entries for the last 24 hours for free users. --- .../ide/history/HistoryV2Manager.coffee | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index 3f231c046b..004221674f 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -82,6 +82,8 @@ define [ viewMode: null nextBeforeTimestamp: null atEnd: false + userHasFullFeature: @$scope.project?.features?.versioning or false + freeHistoryLimitHit: false selection: { label: null updates: [] @@ -232,9 +234,11 @@ define [ @$scope.history.labels = @_sortLabelsByVersionAndDate response.labels.data @_loadUpdates(updatesData.updates) @$scope.history.nextBeforeTimestamp = updatesData.nextBeforeTimestamp - if !updatesData.nextBeforeTimestamp? + if !updatesData.nextBeforeTimestamp? or @$scope.history.freeHistoryLimitHit @$scope.history.atEnd = true @$scope.history.loading = false + if @$scope.history.updates.length == 0 + @$scope.history.loadingFileTree = false .catch (error) => { status, statusText } = error @$scope.history.error = { status, statusText } @@ -387,23 +391,34 @@ define [ _loadUpdates: (updates = []) -> previousUpdate = @$scope.history.updates[@$scope.history.updates.length - 1] - - for update in updates or [] + dateTimeNow = new Date() + timestamp24hoursAgo = dateTimeNow.setDate(dateTimeNow.getDate() - 1) + cutOffIndex = null + + for update, i in updates or [] for user in update.meta.users or [] if user? user.hue = ColorManager.getHueForUserId(user.id) if !previousUpdate? or !moment(previousUpdate.meta.end_ts).isSame(update.meta.end_ts, "day") update.meta.first_in_day = true - + update.selectedFrom = false update.selectedTo = false update.inSelection = false previousUpdate = update + if !@$scope.history.userHasFullFeature and update.meta.end_ts < timestamp24hoursAgo + cutOffIndex = i + @$scope.history.freeHistoryLimitHit = true + break + firstLoad = @$scope.history.updates.length == 0 + if !@$scope.history.userHasFullFeature and cutOffIndex? + updates = updates.slice 0, cutOffIndex + @$scope.history.updates = @$scope.history.updates.concat(updates) From fc424aee9e3a2bc8cf0126db3684845bf14f50e6 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 17 Aug 2018 15:31:59 +0100 Subject: [PATCH 086/104] Adapt styles to avoid layout breaking/showing options that do not make sense when no history entries are loaded. --- services/web/app/views/project/editor/history/toolbarV2.pug | 6 +++--- services/web/public/stylesheets/app/editor/history-v2.less | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug index a683facba4..2acb93854b 100644 --- a/services/web/app/views/project/editor/history/toolbarV2.pug +++ b/services/web/app/views/project/editor/history/toolbarV2.pug @@ -6,7 +6,7 @@ i.fa.fa-spin.fa-refresh |    #{translate("loading")}... span.history-toolbar-selected-version( - ng-show="!history.loadingFileTree && !history.showOnlyLabels && !history.error" + ng-show="!history.loadingFileTree && !history.showOnlyLabels && history.selection.updates.length && !history.error" ) #{translate("browsing_project_as_of")}  time.history-toolbar-time {{ history.selection.updates[0].meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }} span.history-toolbar-selected-version( @@ -19,13 +19,13 @@ button.history-toolbar-btn( ng-click="showAddLabelDialog();" ng-if="!history.showOnlyLabels" - ng-disabled="history.loadingFileTree" + ng-disabled="history.loadingFileTree || history.selection.updates.length == 0" ) i.fa.fa-tag |  #{translate("history_label_this_version")} button.history-toolbar-btn( ng-click="toggleHistoryViewMode();" - ng-disabled="history.loadingFileTree" + ng-disabled="history.loadingFileTree || history.selection.updates.length == 0" ) i.fa.fa-exchange |  #{translate("compare_to_another_version")} diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index cfefbb462c..8c944062b7 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -19,6 +19,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + margin-right: (@line-height-computed / 2); } .history-toolbar-time, .history-toolbar-selected-label { @@ -33,7 +34,7 @@ .btn-xs; padding-left: @padding-small-horizontal; padding-right: @padding-small-horizontal; - margin-left: (@line-height-computed / 2); + margin-right: (@line-height-computed / 2); } .history-toolbar-entries-list { flex: 0 0 @changesListWidth; From 96aa418b94a01bca4dab8b3e010a71179cd83c20 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 17 Aug 2018 16:04:31 +0100 Subject: [PATCH 087/104] Show upgrade prompts when the free history limit is hit. --- .../web/app/views/project/editor/history.pug | 41 ----------------- .../project/editor/history/entriesListV2.pug | 44 +++++++++++++++++++ .../components/historyEntriesList.coffee | 2 + .../stylesheets/app/editor/history-v2.less | 6 +++ 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/services/web/app/views/project/editor/history.pug b/services/web/app/views/project/editor/history.pug index 35de68957f..f968639cca 100644 --- a/services/web/app/views/project/editor/history.pug +++ b/services/web/app/views/project/editor/history.pug @@ -1,45 +1,4 @@ div#history(ng-show="ui.view == 'history'") - span - .upgrade-prompt(ng-if="project.features.versioning === false && ui.view === 'history'") - .message(ng-if="project.owner._id == user.id") - p.text-center: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})} - p.text-center.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")} - ul.list-unstyled - li - i.fa.fa-check   - | #{translate("unlimited_projects")} - - li - i.fa.fa-check   - | #{translate("collabs_per_proj", {collabcount:'Multiple'})} - - li - i.fa.fa-check   - | #{translate("full_doc_history")} - - li - i.fa.fa-check   - | #{translate("sync_to_dropbox")} - - li - i.fa.fa-check   - | #{translate("sync_to_github")} - - li - i.fa.fa-check   - |#{translate("compile_larger_projects")} - p.text-center(ng-controller="FreeTrialModalController") - a.btn.btn-success( - href - ng-class="buttonClass" - ng-click="startFreeTrial('history')" - ) #{translate("start_free_trial")} - - .message(ng-show="project.owner._id != user.id") - p #{translate("ask_proj_owner_to_upgrade_for_history")} - p - a.small(href, ng-click="toggleHistory()") #{translate("cancel")} - include ./history/entriesListV1 include ./history/entriesListV2 diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug index 5e4c4b66c6..640a761296 100644 --- a/services/web/app/views/project/editor/history/entriesListV2.pug +++ b/services/web/app/views/project/editor/history/entriesListV2.pug @@ -6,11 +6,13 @@ aside.change-list( ng-if="!history.showOnlyLabels && !history.error" entries="history.updates" current-user="user" + current-user-is-owner="project.owner._id === user.id" users="projectUsers" load-entries="loadMore()" load-disabled="history.loading || history.atEnd" load-initialize="ui.view == 'history'" is-loading="history.loading" + free-history-limit-hit="history.freeHistoryLimitHit" on-entry-select="handleEntrySelect(selectedEntry)" on-label-delete="handleLabelDelete(label)" ) @@ -134,6 +136,48 @@ script(type="text/ng-template", id="historyEntriesListTpl") .loading(ng-show="$ctrl.isLoading") i.fa.fa-spin.fa-refresh |    #{translate("loading")}... + .history-entries-list-upgrade-prompt( + ng-if="$ctrl.freeHistoryLimitHit && $ctrl.currentUserIsOwner" + ng-controller="FreeTrialModalController" + ) + p #{translate("currently_seeing_only_24_hrs_history")} + p: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})} + ul.list-unstyled + li + i.fa.fa-check   + | #{translate("unlimited_projects")} + + li + i.fa.fa-check   + | #{translate("collabs_per_proj", {collabcount:'Multiple'})} + + li + i.fa.fa-check   + | #{translate("full_doc_history")} + + li + i.fa.fa-check   + | #{translate("sync_to_dropbox")} + + li + i.fa.fa-check   + | #{translate("sync_to_github")} + + li + i.fa.fa-check   + |#{translate("compile_larger_projects")} + p.text-center + a.btn.btn-success( + href + ng-class="buttonClass" + ng-click="startFreeTrial('history')" + ) #{translate("start_free_trial")} + p.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")} + .history-entries-list-upgrade-prompt( + ng-if="$ctrl.freeHistoryLimitHit && !$ctrl.currentUserIsOwner" + ) + p #{translate("currently_seeing_only_24_hrs_history")} + strong #{translate("ask_proj_owner_to_upgrade_for_full_history")} script(type="text/ng-template", id="historyEntryTpl") .history-entry( diff --git a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee index de4e4f1b92..7e97121868 100644 --- a/services/web/public/coffee/ide/history/components/historyEntriesList.coffee +++ b/services/web/public/coffee/ide/history/components/historyEntriesList.coffee @@ -30,6 +30,8 @@ define [ loadInitialize: "<" isLoading: "<" currentUser: "<" + freeHistoryLimitHit: "<" + currentUserIsOwner: "<" onEntrySelect: "&" onLabelDelete: "&" controller: historyEntriesListController diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less index 8c944062b7..35190cd662 100644 --- a/services/web/public/stylesheets/app/editor/history-v2.less +++ b/services/web/public/stylesheets/app/editor/history-v2.less @@ -174,6 +174,12 @@ } } +.history-entries-list-upgrade-prompt { + background-color: #FFF; + margin-bottom: 2px; + padding: 5px 10px; +} + .history-labels-list { .history-entries; overflow-y: auto; From 5974afc2e388614715853742f033d0ebe463d4ed Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 17 Aug 2018 16:17:53 +0100 Subject: [PATCH 088/104] Make sure that at least the last update (i.e. the current state) is shown to free users (even if it happened more than 24 hours ago), to allow labelling. --- services/web/public/coffee/ide/history/HistoryV2Manager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee index 004221674f..66b4375c8e 100644 --- a/services/web/public/coffee/ide/history/HistoryV2Manager.coffee +++ b/services/web/public/coffee/ide/history/HistoryV2Manager.coffee @@ -410,7 +410,7 @@ define [ previousUpdate = update if !@$scope.history.userHasFullFeature and update.meta.end_ts < timestamp24hoursAgo - cutOffIndex = i + cutOffIndex = i or 1 # Make sure that we show at least one entry (to allow labelling). @$scope.history.freeHistoryLimitHit = true break From 5b5ece37a5fbb41be076373465f05d0f1a652c63 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Wed, 15 Aug 2018 14:11:58 -0500 Subject: [PATCH 089/104] Handle sixpack timeouts and include variant in metabase segmentation Render default when sixpack timesout Only convert when variant exists Use Angular cookies module to add variant cookie, which is used on the new subscription page. --- services/web/app/views/subscriptions/plans.pug | 2 +- .../public/coffee/main/new-subscription.coffee | 17 ++++++++--------- services/web/public/coffee/main/plans.coffee | 11 ++++++++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/services/web/app/views/subscriptions/plans.pug b/services/web/app/views/subscriptions/plans.pug index 0a37b90dd8..51efcd5125 100644 --- a/services/web/app/views/subscriptions/plans.pug +++ b/services/web/app/views/subscriptions/plans.pug @@ -14,5 +14,5 @@ block content .content.plans(ng-controller="PlansController") .container(class="more-details" ng-cloak ng-if="plansVariant === 'more-details'") include _plans_page_details_more - .container(ng-cloak ng-if="plansVariant === 'default' || !shouldABTestPlans") + .container(ng-cloak ng-if="plansVariant === 'default' || !shouldABTestPlans || timeout") include _plans_page_details_less diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index 7851171524..e8294a5885 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -4,20 +4,21 @@ define [ "libs/recurly-4.8.5" ], (App)-> - App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking, ccUtils)-> + App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking, ccUtils, ipCookie)-> throw new Error("Recurly API Library Missing.") if typeof recurly is "undefined" $scope.currencyCode = MultiCurrencyPricing.currencyCode $scope.plans = MultiCurrencyPricing.plans $scope.planCode = window.plan_code + $scope.plansVariant = ipCookie('plansVariant') $scope.switchToStudent = ()-> currentPlanCode = window.plan_code planCode = currentPlanCode.replace('collaborator', 'student') - event_tracking.sendMB 'subscription-form-switch-to-student', { plan: window.plan_code } + event_tracking.sendMB 'subscription-form-switch-to-student', { plan: window.plan_code, variant: $scope.plansVariant } window.location = "/user/subscription/new?planCode=#{planCode}¤cy=#{$scope.currencyCode}&cc=#{$scope.data.coupon}" - event_tracking.sendMB "subscription-form", { plan : window.plan_code } + event_tracking.sendMB "subscription-form", { plan : window.plan_code, variant: $scope.plansVariant } $scope.paymentMethod = value: "credit_card" @@ -143,13 +144,14 @@ define [ currencyCode : postData.subscriptionDetails.currencyCode, plan_code : postData.subscriptionDetails.plan_code, coupon_code : postData.subscriptionDetails.coupon_code, - isPaypal : postData.subscriptionDetails.isPaypal + isPaypal : postData.subscriptionDetails.isPaypal, + variant : $scope.plansVariant } $http.post("/user/subscription/create", postData) .then ()-> - event_tracking.sendMB "subscription-submission-success" + event_tracking.sendMB "subscription-submission-success", { variant: $scope.plansVariant } window.location.href = "/user/subscription/thank-you" .catch ()-> $scope.processing = false @@ -234,7 +236,4 @@ define [ {code:'VU',name:'Vanuatu'},{code:'VA',name:'Vatican City'},{code:'VE',name:'Venezuela'},{code:'VN',name:'Vietnam'}, {code:'WK',name:'Wake Island'},{code:'WF',name:'Wallis and Futuna'},{code:'EH',name:'Western Sahara'},{code:'YE',name:'Yemen'}, {code:'ZM',name:'Zambia'},{code:'AX',name:'Åland Islandscode:'} - ] - - sixpack.participate 'plans', ['default', 'more-details'], (chosenVariation, rawResponse)-> - $scope.plansVariant = chosenVariation \ No newline at end of file + ] \ No newline at end of file diff --git a/services/web/public/coffee/main/plans.coffee b/services/web/public/coffee/main/plans.coffee index 258f84b62c..f13dff689f 100644 --- a/services/web/public/coffee/main/plans.coffee +++ b/services/web/public/coffee/main/plans.coffee @@ -145,7 +145,7 @@ define [ } - App.controller "PlansController", ($scope, $modal, event_tracking, abTestManager, MultiCurrencyPricing, $http, sixpack, $filter) -> + App.controller "PlansController", ($scope, $modal, event_tracking, abTestManager, MultiCurrencyPricing, $http, sixpack, $filter, ipCookie) -> $scope.showPlans = false $scope.shouldABTestPlans = window.shouldABTestPlans @@ -154,7 +154,12 @@ define [ sixpack.participate 'plans-details', ['default', 'more-details'], (chosenVariation, rawResponse)-> if rawResponse?.status != 'failed' $scope.plansVariant = chosenVariation + expiration = new Date(); + expiration.setDate(expiration.getDate() + 5); + ipCookie('plansVariant', chosenVariation, {expires: expiration}) event_tracking.send 'subscription-funnel', 'plans-page-loaded', chosenVariation + else + $scope.timeout = true $scope.showPlans = true @@ -185,9 +190,9 @@ define [ if $scope.ui.view == "annual" plan = "#{plan}_annual" plan = eventLabel(plan, location) - event_tracking.sendMB 'plans-page-start-trial', {plan} + event_tracking.sendMB 'plans-page-start-trial', {plan, variant: $scope.plansVariant} event_tracking.send 'subscription-funnel', 'sign_up_now_button', plan - if $scope.shouldABTestPlans + if $scope.plansVariant sixpack.convert 'plans-details' $scope.switchToMonthly = (e, location) -> From f5513f36ca2222fb4f360cbbea420949c294e878 Mon Sep 17 00:00:00 2001 From: Tim Alby Date: Fri, 17 Aug 2018 15:08:59 +0200 Subject: [PATCH 090/104] add param to skip affiliation creation on user create --- .../app/coffee/Features/User/UserCreator.coffee | 16 ++++++++++------ .../unit/coffee/User/UserCreatorTests.coffee | 8 ++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/services/web/app/coffee/Features/User/UserCreator.coffee b/services/web/app/coffee/Features/User/UserCreator.coffee index 2dfa7f5ac0..17248b38ae 100644 --- a/services/web/app/coffee/Features/User/UserCreator.coffee +++ b/services/web/app/coffee/Features/User/UserCreator.coffee @@ -6,15 +6,18 @@ metrics = require('metrics-sharelatex') module.exports = UserCreator = - createNewUser: (opts, callback)-> - logger.log opts:opts, "creating new user" + createNewUser: (attributes, options, callback = (error, user) ->)-> + if arguments.length == 2 + callback = options + options = {} + logger.log user: attributes, "creating new user" user = new User() - username = opts.email.match(/^[^@]*/) - if !opts.first_name? or opts.first_name == "" - opts.first_name = username[0] + username = attributes.email.match(/^[^@]*/) + if !attributes.first_name? or attributes.first_name == "" + attributes.first_name = username[0] - for key, value of opts + for key, value of attributes user[key] = value user.ace.syntaxValidation = true @@ -27,6 +30,7 @@ module.exports = UserCreator = user.save (err)-> callback(err, user) + return if options?.skip_affiliation # call addaffiliation after the main callback so it runs in the # background. There is no guaranty this will run so we must no rely on it addAffiliation user._id, user.email, (error) -> diff --git a/services/web/test/unit/coffee/User/UserCreatorTests.coffee b/services/web/test/unit/coffee/User/UserCreatorTests.coffee index f9d88e4cf8..5f3bf60e6a 100644 --- a/services/web/test/unit/coffee/User/UserCreatorTests.coffee +++ b/services/web/test/unit/coffee/User/UserCreatorTests.coffee @@ -88,3 +88,11 @@ describe "UserCreator", -> process.nextTick () => sinon.assert.calledWith(@addAffiliation, user._id, user.email) done() + + it "should not add affiliation if skipping", (done)-> + attributes = email: @email + options = skip_affiliation: true + @UserCreator.createNewUser attributes, options, (err, user) => + process.nextTick () => + sinon.assert.notCalled(@addAffiliation) + done() From 8d6505b518f74b8797eb673293a3e5b24128f77a Mon Sep 17 00:00:00 2001 From: Tim Alby Date: Fri, 17 Aug 2018 15:13:24 +0200 Subject: [PATCH 091/104] log institutions API errors --- .../web/app/coffee/Features/Institutions/InstitutionsAPI.coffee | 2 ++ services/web/test/unit/coffee/User/UserCreatorTests.coffee | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index 8ce39d68e3..c62a8a01bc 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -80,6 +80,8 @@ makeAffiliationRequest = (requestOptions, callback = (error) ->) -> errorMessage = "#{response.statusCode}: #{body.errors}" else errorMessage = "#{requestOptions.defaultErrorMessage}: #{response.statusCode}" + + logger.err path: requestOptions.path, body: requestOptions.body, errorMessage return callback(new Error(errorMessage)) callback(null, body) diff --git a/services/web/test/unit/coffee/User/UserCreatorTests.coffee b/services/web/test/unit/coffee/User/UserCreatorTests.coffee index 5f3bf60e6a..d9118e450f 100644 --- a/services/web/test/unit/coffee/User/UserCreatorTests.coffee +++ b/services/web/test/unit/coffee/User/UserCreatorTests.coffee @@ -20,7 +20,7 @@ describe "UserCreator", -> @addAffiliation = sinon.stub().yields() @UserCreator = SandboxedModule.require modulePath, requires: "../../models/User": User:@UserModel - "logger-sharelatex":{log:->} + "logger-sharelatex":{ log: sinon.stub(), err: sinon.stub() } 'metrics-sharelatex': {timeAsyncMethod: ()->} "../Institutions/InstitutionsAPI": addAffiliation: @addAffiliation From f8b85cb8482a5fbe8b66444f3b3a4d95a97ac811 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Tue, 14 Aug 2018 15:29:03 -0500 Subject: [PATCH 092/104] Move link mixins to mixins folder --- services/web/app/views/{_mixins_links.pug => _mixins/links.pug} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/web/app/views/{_mixins_links.pug => _mixins/links.pug} (100%) diff --git a/services/web/app/views/_mixins_links.pug b/services/web/app/views/_mixins/links.pug similarity index 100% rename from services/web/app/views/_mixins_links.pug rename to services/web/app/views/_mixins/links.pug From 45cc278acbceaecc9c88a5fa3b6be9a8d6e57e72 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Tue, 14 Aug 2018 15:30:13 -0500 Subject: [PATCH 093/104] Add FAQ search mixin --- services/web/app/views/_mixins/faq_search.pug | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 services/web/app/views/_mixins/faq_search.pug diff --git a/services/web/app/views/_mixins/faq_search.pug b/services/web/app/views/_mixins/faq_search.pug new file mode 100644 index 0000000000..b5d00f9832 --- /dev/null +++ b/services/web/app/views/_mixins/faq_search.pug @@ -0,0 +1,21 @@ +mixin faq_search(headerText, headerClass) + - if(typeof(settings.algolia) != "undefined" && typeof(settings.algolia.indexes) != "undefined" && typeof(settings.algolia.indexes.wiki) != "undefined") + if headerText + div(class=headerClass) #{headerText} + .wiki(ng-controller="SearchWikiController") + form.project-search.form-horizontal(role="form") + .form-group.has-feedback.has-feedback-left + .col-sm-12 + input.form-control(type='text', ng-model='searchQueryText', ng-keyup='search()', placeholder="Search help library....") + i.fa.fa-search.form-control-feedback-left + i.fa.fa-times.form-control-feedback( + ng-click="clearSearchText()", + style="cursor: pointer;", + ng-show="searchQueryText.length > 0" + ) + + .row + .col-md-12(ng-cloak) + a(ng-href='{{hit.url}}',ng-repeat='hit in hits').search-result.card.card-thin + span(ng-bind-html='hit.name') + div.search-result-content(ng-show="hit.content != ''", ng-bind-html='hit.content') From 47c51257bbbaff6a4f57f3dc9ef3b17f83d37f81 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 20 Aug 2018 11:51:12 +0100 Subject: [PATCH 094/104] Explicitly set the user default email for the delete account modal controller. --- .../coffee/main/account-settings.coffee | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/services/web/public/coffee/main/account-settings.coffee b/services/web/public/coffee/main/account-settings.coffee index 82d323110e..b12b8f3dde 100644 --- a/services/web/public/coffee/main/account-settings.coffee +++ b/services/web/public/coffee/main/account-settings.coffee @@ -1,7 +1,7 @@ define [ "base" ], (App) -> - App.controller "AccountSettingsController", ["$scope", "$http", "$modal", "event_tracking", ($scope, $http, $modal, event_tracking) -> + App.controller "AccountSettingsController", ["$scope", "$http", "$modal", "event_tracking", "UserAffiliationsDataService", ($scope, $http, $modal, event_tracking, UserAffiliationsDataService) -> $scope.subscribed = true $scope.unsubscribe = () -> @@ -21,8 +21,15 @@ define [ $scope.deleteAccount = () -> modalInstance = $modal.open( templateUrl: "deleteAccountModalTemplate" - controller: "DeleteAccountModalController", - scope: $scope + controller: "DeleteAccountModalController" + resolve: + userDefaultEmail: () -> + UserAffiliationsDataService + .getUserEmails() + .then (userEmails) -> + defaultEmailDetails = _.find userEmails, (userEmail) -> userEmail.default + return defaultEmailDetails?.email or null + .catch () -> null ) $scope.upgradeIntegration = (service) -> @@ -30,8 +37,8 @@ define [ ] App.controller "DeleteAccountModalController", [ - "$scope", "$modalInstance", "$timeout", "$http", - ($scope, $modalInstance, $timeout, $http) -> + "$scope", "$modalInstance", "$timeout", "$http", "userDefaultEmail", + ($scope, $modalInstance, $timeout, $http, userDefaultEmail) -> $scope.state = isValid : false deleteText: "" @@ -46,7 +53,7 @@ define [ , 700 $scope.checkValidation = -> - $scope.state.isValid = $scope.state.deleteText == $scope.email and $scope.state.password.length > 0 + $scope.state.isValid = userDefaultEmail? and $scope.state.deleteText == userDefaultEmail and $scope.state.password.length > 0 $scope.delete = () -> $scope.state.inflight = true From 042c959d3acb5e701d03a2ff1b2ceff5306944cc Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 20 Aug 2018 12:15:15 +0100 Subject: [PATCH 095/104] Move default email checking logic into the data service. --- services/web/public/coffee/main/account-settings.coffee | 5 ++--- .../factories/UserAffiliationsDataService.coffee | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/services/web/public/coffee/main/account-settings.coffee b/services/web/public/coffee/main/account-settings.coffee index b12b8f3dde..cac35394a1 100644 --- a/services/web/public/coffee/main/account-settings.coffee +++ b/services/web/public/coffee/main/account-settings.coffee @@ -25,9 +25,8 @@ define [ resolve: userDefaultEmail: () -> UserAffiliationsDataService - .getUserEmails() - .then (userEmails) -> - defaultEmailDetails = _.find userEmails, (userEmail) -> userEmail.default + .getUserDefaultEmail() + .then (defaultEmailDetails) -> return defaultEmailDetails?.email or null .catch () -> null ) diff --git a/services/web/public/coffee/main/affiliations/factories/UserAffiliationsDataService.coffee b/services/web/public/coffee/main/affiliations/factories/UserAffiliationsDataService.coffee index cbcadf7e67..ca1ffef168 100644 --- a/services/web/public/coffee/main/affiliations/factories/UserAffiliationsDataService.coffee +++ b/services/web/public/coffee/main/affiliations/factories/UserAffiliationsDataService.coffee @@ -31,6 +31,10 @@ define [ $http.get "/user/emails" .then (response) -> response.data + getUserDefaultEmail = () -> + getUserEmails().then (userEmails) -> + _.find userEmails, (userEmail) -> userEmail.default + getUniversitiesFromCountry = (country) -> if universities[country.code]? universitiesFromCountry = universities[country.code] @@ -118,6 +122,7 @@ define [ getDefaultRoleHints getDefaultDepartmentHints getUserEmails + getUserDefaultEmail getUniversitiesFromCountry getUniversityDomainFromPartialDomainInput getUniversityDetails From 09efced3523988488c3a9370d98a017aa1a9ef84 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe Date: Fri, 27 Jul 2018 15:50:20 -0500 Subject: [PATCH 096/104] Add pagination mixin and color variables --- services/web/app/views/mixins/_pagination.pug | 74 +++++++++++++++++++ .../public/stylesheets/core/ol-variables.less | 15 ++++ services/web/public/stylesheets/ol-style.less | 1 + 3 files changed, 90 insertions(+) create mode 100644 services/web/app/views/mixins/_pagination.pug diff --git a/services/web/app/views/mixins/_pagination.pug b/services/web/app/views/mixins/_pagination.pug new file mode 100644 index 0000000000..4ac2d924e8 --- /dev/null +++ b/services/web/app/views/mixins/_pagination.pug @@ -0,0 +1,74 @@ +mixin paginate(pages, page_path, max_btns) + //- @param pages.current_page the current page viewed + //- @param pages.total_pages previously calculated, + //- based on total entries and entries per page + //- @param page_path the relative path, minus a trailing slash and page param + //- @param max_btns max number of buttons on either side of the current page + //- button and excludes first, prev, next, last + + if pages && pages.current_page && pages.total_pages + - var max_btns = max_btns || 4 + - var prev_page = Math.max(parseInt(pages.current_page, 10) - max_btns, 1) + - var next_page = parseInt(pages.current_page, 10) + 1 + - var next_index = 0; + - var full_page_path = page_path + "/page/" + + nav(role="navigation" aria-label="Pagination Navigation") + ul.pagination + if pages.current_page > 1 + li + a( + aria-label="Go to first page" + href=page_path + ) « First + li + a( + aria-label="Go to previous page" + href=full_page_path + (parseInt(pages.current_page, 10) - 1) + rel="prev" + ) ‹ Prev + + if pages.current_page - max_btns > 1 + li + span … + + while prev_page < pages.current_page + li + a( + aria-label="Go to page " + prev_page + href=full_page_path + prev_page + ) #{prev_page} + - prev_page++ + + li(class="active") + span( + aria-label="Current Page, Page " + pages.current_page + aria-current="true" + ) #{pages.current_page} + + if pages.current_page < pages.total_pages + while next_page <= pages.total_pages && next_index < max_btns + li + a( + aria-label="Go to page " + next_page + href=full_page_path + next_page + ) #{next_page} + - next_page++ + - next_index++ + + if next_page <= pages.total_pages + li + span … + + li + a( + aria-label="Go to next page" + href=full_page_path + (parseInt(pages.current_page, 10) + 1) + rel="next" + ) Next › + + li + a( + aria-label="Go to last page" + href=full_page_path + pages.total_pages + ) Last » diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less index e6a20c6ad8..7c22b1f01c 100644 --- a/services/web/public/stylesheets/core/ol-variables.less +++ b/services/web/public/stylesheets/core/ol-variables.less @@ -277,6 +277,21 @@ @chat-new-message-textarea-bg : @ol-blue-gray-1; @chat-new-message-textarea-color : @ol-blue-gray-6; +// Pagination +@pagination-active-bg : @ol-dark-green; +@pagination-active-border : @gray-lighter; +@pagination-active-color : #FFF; +@pagination-bg : #FFF; +@pagination-border : @gray-lighter; +@pagination-color : @ol-dark-green; +@pagination-disabled-color : @gray-dark; +@pagination-disabled-bg : @gray-lightest; +@pagination-disabled-border : @gray-lighter; +@pagination-hover-color : @ol-dark-green; +@pagination-hover-bg : @gray-lightest; +@pagination-hover-border : @gray-lighter; + + // PDF @pdf-top-offset : @toolbar-small-height; @pdf-bg : @ol-blue-gray-1; diff --git a/services/web/public/stylesheets/ol-style.less b/services/web/public/stylesheets/ol-style.less index 774e70a2ab..2be148a497 100644 --- a/services/web/public/stylesheets/ol-style.less +++ b/services/web/public/stylesheets/ol-style.less @@ -8,6 +8,7 @@ @import "_ol_style_includes.less"; @import "components/embed-responsive.less"; @import "components/icons.less"; +@import "components/pagination.less"; // Pages @import "app/about.less"; From 4dee3fd5e1d9e35fabd5ce4b71c68d47c2df8dfa Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 20 Aug 2018 17:02:55 +0100 Subject: [PATCH 097/104] Update frontend unit tests. --- .../coffee/ide/history/HistoryV2ManagerTests.coffee | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee b/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee index 2fc5c5b3b6..542ee3e8eb 100644 --- a/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee +++ b/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee @@ -4,16 +4,21 @@ define ['ide/history/HistoryV2Manager'], (HistoryV2Manager) -> @scope = $watch: sinon.stub() $on: sinon.stub() + project: + features: + versioning: true @ide = {} @historyManager = new HistoryV2Manager(@ide, @scope) - it "should setup the history scope on intialization", -> + it "should setup the history scope on initialization", -> expect(@scope.history).to.deep.equal({ isV2: true updates: [] viewMode: null nextBeforeTimestamp: null atEnd: false + userHasFullFeature: true + freeHistoryLimitHit: false selection: { label: null updates: [] @@ -32,6 +37,12 @@ define ['ide/history/HistoryV2Manager'], (HistoryV2Manager) -> selectedFile: null }) + + it "should setup history without full access to the feature if the project does not have versioning", -> + @scope.project.features.versioning = false + @historyManager = new HistoryV2Manager(@ide, @scope) + expect(@scope.history.userHasFullFeature).to.equal false + describe "_perDocSummaryOfUpdates", -> it "should return the range of updates for the docs", -> result = @historyManager._perDocSummaryOfUpdates([{ From 7a253e56ef94b90175b037a4b1384de9fc4cf9d8 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 21 Aug 2018 09:24:48 +0100 Subject: [PATCH 098/104] Download all log files from compiles.sharelatex.env (#833) * add a couple of missing buildPdfDownloadUrl * only download pdfs from compile server when streaming * use compiles.env for resonse files * update config to use clsi_lb --- services/web/config/settings.defaults.coffee | 3 ++- .../coffee/ide/pdf/controllers/PdfController.coffee | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 832c3855b5..59d6e0aa89 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -135,6 +135,7 @@ module.exports = settings = url: "http://#{process.env['FILESTORE_HOST'] or 'localhost'}:3009" clsi: url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013" + # url: "http://#{process.env['CLSI_LB_HOST']}:3014" backendGroupName: undefined templates: url: "http://#{process.env['TEMPLATES_HOST'] or 'localhost'}:3007" @@ -340,7 +341,7 @@ module.exports = settings = # disablePerUserCompiles: true # Domain the client (pdfjs) should download the compiled pdf from - # pdfDownloadDomain: "http://compiles.sharelatex.test:3014" + # pdfDownloadDomain: "http://clsi-lb:3014" # Maximum size of text documents in the real-time editing system. max_doc_length: 2 * 1024 * 1024 # 2mb diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index c717ef6b82..40264e1be0 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -225,7 +225,8 @@ define [ }, {params: params} buildPdfDownloadUrl = (pdfDownloadDomain, path)-> - if pdfDownloadDomain? + #we only download builds from compiles server for security reasons + if pdfDownloadDomain? and path.indexOf("build") != -1 return "#{pdfDownloadDomain}#{path}" else return path @@ -378,14 +379,15 @@ define [ compileGroup:ide.compileGroup clsiserverid:ide.clsiServerId if file?.url? # FIXME clean this up when we have file.urls out consistently - opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, file.url + opts.url = file.url else if file?.build? - opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, "/project/#{$scope.project_id}/build/#{file.build}/output/#{name}" + opts.url = "/project/#{$scope.project_id}/build/#{file.build}/output/#{name}" else - opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, "/project/#{$scope.project_id}/output/#{name}" + opts.url = "/project/#{$scope.project_id}/output/#{name}" # check if we need to bust cache (build id is unique so don't need it in that case) if not file?.build? opts.params.cache_bust = "#{Date.now()}" + opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, opts.url return $http(opts) # accumulate the log entries From 473063cf3d2006cad75947d3a0a6013a82531cc6 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 21 Aug 2018 10:36:18 +0100 Subject: [PATCH 099/104] set rclient.expire for server ttl even when there is no server id --- .../web/app/coffee/Features/Compile/ClsiCookieManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee b/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee index 2bee2ba5e6..f2c982a077 100644 --- a/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee +++ b/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee @@ -49,7 +49,7 @@ module.exports = (backendGroup)-> return callback() serverId = @_parseServerIdFromResponse(response) if !serverId? # We don't get a cookie back if it hasn't changed - return callback() + return rclient.expire(@buildKey(project_id), Settings.clsiCookie.ttl, callback) if rclient_secondary? @_setServerIdInRedis rclient_secondary, project_id, serverId @_setServerIdInRedis rclient, project_id, serverId, (err) -> From ab1848d0aee4a6f5abac2791d038eba5dc93fa8c Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 22 Aug 2018 14:47:15 +0100 Subject: [PATCH 100/104] Add a trusted filter for iframe downloads (#836) * add trusted helper to iframe downloads --- services/web/app/views/project/editor/pdf.pug | 2 +- .../public/coffee/ide/pdf/controllers/PdfController.coffee | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/services/web/app/views/project/editor/pdf.pug b/services/web/app/views/project/editor/pdf.pug index 26de2f35b1..d485d24700 100644 --- a/services/web/app/views/project/editor/pdf.pug +++ b/services/web/app/views/project/editor/pdf.pug @@ -305,7 +305,7 @@ div.full-size.pdf(ng-controller="PdfController") dbl-click-callback="syncToCode" ) iframe( - ng-src="{{ pdf.url }}" + ng-src="{{ pdf.url | trusted }}" ng-if="settings.pdfViewer == 'native'" ) diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index 40264e1be0..79b770780a 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -12,6 +12,10 @@ define [ # and then again on ack. AUTO_COMPILE_DEBOUNCE = 2000 + App.filter('trusted', ['$sce', ($sce)-> + return (url)-> return $sce.trustAsResourceUrl(url); + ]) + App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking, logHintsFeedback, localStorage) -> # enable per-user containers by default perUserCompile = true From cfaa8444dbb39375e4abe57be3fdfe8e095b2325 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 22 Aug 2018 16:33:15 +0100 Subject: [PATCH 101/104] null check path when building a pdf download url if there is an error just the domain is currently returned, empty string is better for us --- .../web/public/coffee/ide/pdf/controllers/PdfController.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index 79b770780a..b4968b7d8a 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -230,7 +230,7 @@ define [ buildPdfDownloadUrl = (pdfDownloadDomain, path)-> #we only download builds from compiles server for security reasons - if pdfDownloadDomain? and path.indexOf("build") != -1 + if pdfDownloadDomain? and path? and path.indexOf("build") != -1 return "#{pdfDownloadDomain}#{path}" else return path From 8d72fc78fcbbf151238d5546a98ed7c88d03e4cc Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Wed, 22 Aug 2018 18:31:29 +0100 Subject: [PATCH 102/104] send licences graph request to v1 for data instead of analytics --- .../Analytics/AnalyticsController.coffee | 10 ++++++++++ .../Features/Analytics/AnalyticsRouter.coffee | 4 +++- .../Institutions/InstitutionsAPI.coffee | 7 +++++++ .../Analytics/AnalyticsControllerTests.coffee | 20 +++++++++++++++++++ .../Institutions/InstitutionsAPITests.coffee | 20 +++++++++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee index 38029219ee..7773064481 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee @@ -1,6 +1,7 @@ AnalyticsManager = require "./AnalyticsManager" Errors = require "../Errors/Errors" AuthenticationController = require("../Authentication/AuthenticationController") +InstitutionsAPI = require("../Institutions/InstitutionsAPI") GeoIpLookup = require '../../infrastructure/GeoIpLookup' module.exports = AnalyticsController = @@ -23,6 +24,15 @@ module.exports = AnalyticsController = AnalyticsManager.recordEvent user_id, req.params.event, req.body, (error) -> respondWith(error, res, next) + licences: (req, res, next) -> + AuthenticationController.getLoggedInUserId(req) or req.sessionID + {resource_id, start_date, end_date, lag} = req.query + InstitutionsAPI.getInstitutionLicences resource_id, start_date, end_date, lag, (error, licences) -> + if error? + res.send 503 + else + res.send licences + respondWith = (error, res, next) -> if error instanceof Errors.ServiceNotConfiguredError # ignore, no-op diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee index 06ca2bfa1c..fb0f890241 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee @@ -9,6 +9,8 @@ module.exports = webRouter.put '/editingSession/:projectId', AnalyticsController.updateEditingSession + webRouter.get '/graphs/licences', AnalyticsController.licences + publicApiRouter.use '/analytics/graphs', AuthenticationController.httpAuth, AnalyticsProxy.call('/graphs') @@ -23,4 +25,4 @@ module.exports = publicApiRouter.use '/analytics/uniExternalCollaboration', AuthenticationController.httpAuth, - AnalyticsProxy.call('/uniExternalCollaboration') \ No newline at end of file + AnalyticsProxy.call('/uniExternalCollaboration') diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index d1b2a69816..def1426a28 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -11,6 +11,13 @@ module.exports = InstitutionsAPI = defaultErrorMessage: "Couldn't get institution affiliations" }, callback + getInstitutionLicences: (institutionId, startDate, endDate, lag, callback = (error, body) ->) -> + makeAffiliationRequest { + method: 'GET' + path: "/api/v2/institutions/#{institutionId.toString()}/institution_licences" + body: {start_date: startDate, end_date: endDate, lag} + defaultErrorMessage: "Couldn't get institution affiliations" + }, callback getUserAffiliations: (userId, callback = (error, body) ->) -> makeAffiliationRequest { diff --git a/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee b/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee index c3e3802f37..0aa0517074 100644 --- a/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee +++ b/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee @@ -17,9 +17,13 @@ describe 'AnalyticsController', -> updateEditingSession: sinon.stub().callsArgWith(3) recordEvent: sinon.stub().callsArgWith(3) + @InstitutionsAPI = + getInstitutionLicences: sinon.stub().callsArgWith(4) + @controller = SandboxedModule.require modulePath, requires: "./AnalyticsManager":@AnalyticsManager "../Authentication/AuthenticationController":@AuthenticationController + "../Institutions/InstitutionsAPI":@InstitutionsAPI "logger-sharelatex": log:-> '../../infrastructure/GeoIpLookup': @GeoIpLookup = @@ -66,3 +70,19 @@ describe 'AnalyticsController', -> @controller.recordEvent @req, @res @AnalyticsManager.recordEvent.calledWith(@req.sessionID, @req.params["event"], @req.body).should.equal true done() + + describe "licences", -> + beforeEach -> + @req = + query: + resource_id:1 + start_date:'1514764800' + end_date:'1530662400' + resource_type:'institution' + sessionID: "sessionIDHere" + session: {} + + it "should trigger institutions api to fetch licences graph data", (done)-> + @controller.licences @req, @res + @InstitutionsAPI.getInstitutionLicences.calledWith(@req.query["resource_id"], @req.query["start_date"], @req.query["end_date"], @req.query["lag"]).should.equal true + done() diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee index 04f0b61e8e..759cb8bfa4 100644 --- a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee +++ b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee @@ -41,6 +41,26 @@ describe "InstitutionsAPI", -> body.should.equal responseBody done() + describe 'getInstitutionLicences', -> + it 'get licences', (done)-> + @institutionId = 123 + responseBody = {"lag":"monthly","data":[{"key":"users","values":[{"x":"2018-01-01","y":1}]}]} + @request.yields(null, { statusCode: 200 }, responseBody) + startDate = '1417392000' + endDate = '1420848000' + @InstitutionsAPI.getInstitutionLicences @institutionId, startDate, endDate, 'monthly', (err, body) => + should.not.exist(err) + @request.calledOnce.should.equal true + requestOptions = @request.lastCall.args[0] + expectedUrl = "v1.url/api/v2/institutions/#{@institutionId}/institution_licences" + requestOptions.url.should.equal expectedUrl + requestOptions.method.should.equal 'GET' + requestOptions.body['start_date'].should.equal startDate + requestOptions.body['end_date'].should.equal endDate + requestOptions.body.lag.should.equal 'monthly' + body.should.equal responseBody + done() + describe 'getUserAffiliations', -> it 'get affiliations', (done)-> responseBody = [{ foo: 'bar' }] From 753fb02c056884525a1067266ac43f5977b101da Mon Sep 17 00:00:00 2001 From: Tim Alby Date: Mon, 20 Aug 2018 16:29:44 +0200 Subject: [PATCH 103/104] always return an array when getting affiliations --- .../Institutions/InstitutionsAPI.coffee | 4 ++-- .../Institutions/InstitutionsAPITests.coffee | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index d1b2a69816..3d8da2ee80 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -9,7 +9,7 @@ module.exports = InstitutionsAPI = method: 'GET' path: "/api/v2/institutions/#{institutionId.toString()}/affiliations" defaultErrorMessage: "Couldn't get institution affiliations" - }, callback + }, (error, body) -> callback(error, body or []) getUserAffiliations: (userId, callback = (error, body) ->) -> @@ -17,7 +17,7 @@ module.exports = InstitutionsAPI = method: 'GET' path: "/api/v2/users/#{userId.toString()}/affiliations" defaultErrorMessage: "Couldn't get user affiliations" - }, callback + }, (error, body) -> callback(error, body or []) addAffiliation: (userId, email, affiliationOptions, callback) -> diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee index 04f0b61e8e..9588ea32e7 100644 --- a/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee +++ b/services/web/test/unit/coffee/Institutions/InstitutionsAPITests.coffee @@ -11,12 +11,12 @@ describe "InstitutionsAPI", -> beforeEach -> @logger = err: sinon.stub(), log: -> - settings = apis: { v1: { url: 'v1.url', user: '', pass: '' } } + @settings = apis: { v1: { url: 'v1.url', user: '', pass: '' } } @request = sinon.stub() @InstitutionsAPI = SandboxedModule.require modulePath, requires: "logger-sharelatex": @logger "metrics-sharelatex": timeAsyncMethod: sinon.stub() - 'settings-sharelatex': settings + 'settings-sharelatex': @settings 'request': @request @stubbedUser = @@ -41,6 +41,14 @@ describe "InstitutionsAPI", -> body.should.equal responseBody done() + it 'handle empty response', (done)-> + @settings.apis = null + @InstitutionsAPI.getInstitutionAffiliations @institutionId, (err, body) => + should.not.exist(err) + expect(body).to.be.a 'Array' + body.length.should.equal 0 + done() + describe 'getUserAffiliations', -> it 'get affiliations', (done)-> responseBody = [{ foo: 'bar' }] @@ -65,6 +73,14 @@ describe "InstitutionsAPI", -> err.message.should.have.string body.errors done() + it 'handle empty response', (done)-> + @settings.apis = null + @InstitutionsAPI.getUserAffiliations @stubbedUser._id, (err, body) => + should.not.exist(err) + expect(body).to.be.a 'Array' + body.length.should.equal 0 + done() + describe 'addAffiliation', -> beforeEach -> @request.callsArgWith(1, null, { statusCode: 201 }) From bd721d52f4aaf820264d3e0d314b1487727106a4 Mon Sep 17 00:00:00 2001 From: hugh-obrien Date: Thu, 23 Aug 2018 14:39:48 +0100 Subject: [PATCH 104/104] review fixes and moving licences endpoint to module --- .../app/coffee/Features/Analytics/AnalyticsController.coffee | 3 +-- .../web/app/coffee/Features/Analytics/AnalyticsRouter.coffee | 2 -- .../app/coffee/Features/Institutions/InstitutionsAPI.coffee | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee index 7773064481..e407feb488 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee @@ -25,11 +25,10 @@ module.exports = AnalyticsController = respondWith(error, res, next) licences: (req, res, next) -> - AuthenticationController.getLoggedInUserId(req) or req.sessionID {resource_id, start_date, end_date, lag} = req.query InstitutionsAPI.getInstitutionLicences resource_id, start_date, end_date, lag, (error, licences) -> if error? - res.send 503 + next(error) else res.send licences diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee index fb0f890241..57b131326f 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee @@ -9,8 +9,6 @@ module.exports = webRouter.put '/editingSession/:projectId', AnalyticsController.updateEditingSession - webRouter.get '/graphs/licences', AnalyticsController.licences - publicApiRouter.use '/analytics/graphs', AuthenticationController.httpAuth, AnalyticsProxy.call('/graphs') diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index def1426a28..e2fdab68d7 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -16,7 +16,7 @@ module.exports = InstitutionsAPI = method: 'GET' path: "/api/v2/institutions/#{institutionId.toString()}/institution_licences" body: {start_date: startDate, end_date: endDate, lag} - defaultErrorMessage: "Couldn't get institution affiliations" + defaultErrorMessage: "Couldn't get institution licences" }, callback getUserAffiliations: (userId, callback = (error, body) ->) ->