From c0a691b2630edaf6ac4657a71a3341e735cd5eea Mon Sep 17 00:00:00 2001 From: Chasen Le Hara Date: Wed, 20 Sep 2017 16:18:45 -0700 Subject: [PATCH 1/2] Add a bar that advertises the community survey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `survey-ad` control is shown at the bottom of every page. If you click almost anywhere in the bar, the survey landing page will open in a new tab/window. At the far right of the bar, there’s a close button that will animate the bar out of view and store the current time in localStorage. When the page first loads, we check for the presence of that localStorage item to decide whether the bar should be shown or not. The bar is only enabled for desktop (1000px+ wide) screens. The only bug I’ve found is when you use search while the bar is still present; in that situation, you can see all of the search results, but the scroll bar goes under the bar. I don’t think that’s a blocker. This is related to https://github.com/canjs/canjs/issues/3548 --- static/canjs.js | 6 +++++ static/canjs.less | 1 + static/layout.less | 14 +++++++++- static/survey-ad.js | 50 ++++++++++++++++++++++++++++++++++++ static/survey-ad.less | 42 ++++++++++++++++++++++++++++++ templates/content.mustache | 3 ++- templates/survey-ad.mustache | 8 ++++++ 7 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 static/survey-ad.js create mode 100644 static/survey-ad.less create mode 100644 templates/survey-ad.mustache diff --git a/static/canjs.js b/static/canjs.js index d0b25154..e48ffa94 100644 --- a/static/canjs.js +++ b/static/canjs.js @@ -5,6 +5,7 @@ $ = require("jquery"); var debounce = require("lodash/debounce"); var loader = new LoadingBar('blue'); var SearchControl = require('./search'); +var SurveyAdControl = require('./survey-ad'); // state var $articleContainer, @@ -23,6 +24,7 @@ var $articleContainer, scrollPositionInterval, currentHref, searchControl, + surveyAdControl, hasShownSearch; (function() { @@ -120,6 +122,10 @@ function init() { }); } + if (!surveyAdControl) { + surveyAdControl = new SurveyAdControl("survey-ad"); + } + hasShownSearch = true; } diff --git a/static/canjs.less b/static/canjs.less index a1cc5c46..33abb727 100644 --- a/static/canjs.less +++ b/static/canjs.less @@ -22,3 +22,4 @@ @import "screenshots.less"; @import "loading-bar.less"; @import "search.less"; +@import "survey-ad.less"; diff --git a/static/layout.less b/static/layout.less index 2dfb7366..f09fc3e3 100644 --- a/static/layout.less +++ b/static/layout.less @@ -6,7 +6,19 @@ flex-wrap: nowrap; box-sizing: border-box; height: 100%; - -webkit-overflow-scrolling: touch + -webkit-overflow-scrolling: touch; + padding-bottom: 0; + transition: padding-bottom @transition-speed ease; + + @media screen and (min-width: @breakpoint) { + // Make some space for the survey-ad + &.survey-ad-showing { + padding-bottom: 3rem; + .search-results-container { + padding-bottom: 3rem; + } + } + } } #left { -ms-flex-wrap: wrap; diff --git a/static/survey-ad.js b/static/survey-ad.js new file mode 100644 index 00000000..6794aaeb --- /dev/null +++ b/static/survey-ad.js @@ -0,0 +1,50 @@ +var Control = require("can-control"); + +module.exports = Control.extend({ + /* This should only happen once */ + init: function() { + var surveyAdElement = this.element; + + // Always show the close button since JS is active + var closeButton = surveyAdElement.querySelector('.close'); + if (closeButton.classList) {// Only enable the close button in IE10+ + closeButton.style.display = 'inline-block'; + } + + // Look up the user’s preference in localStorage + try { + var storageKey = 'survey-ad-closed'; + var didClose = window.localStorage.getItem(storageKey); + if (didClose) { + // Immediately hide this element + surveyAdElement.style.display = 'none'; + // .survey-ad-showing is used to change padding-bottom on the rest of the content + document.getElementById('everything').classList.remove('survey-ad-showing'); + } + } catch (error) { + console.info('Caught localStorage error:', error); + } + }, + /* This event will fire when the user clicks the X button */ + '{element} .close click': function() { + + // Add .hidden so the element animates out of view + this.element.classList.add('hidden'); + + // .survey-ad-showing is used to change padding-bottom on the rest of the content + document.getElementById('everything').classList.remove('survey-ad-showing'); + + // Try to remember the user’s preference in localStorage + try { + // Get the current time; we don’t make use of this right now, + // but in the future if we want to re-enable the link after a + // certain amount of time or for a particular event, we can + // more easily do that :) + var currentTime = (new Date()).getTime(); + var storageKey = 'survey-ad-closed'; + window.localStorage.setItem(storageKey, currentTime); + } catch (error) { + console.info('Caught localStorage error:', error); + } + } +}); diff --git a/static/survey-ad.less b/static/survey-ad.less new file mode 100644 index 00000000..838d80e1 --- /dev/null +++ b/static/survey-ad.less @@ -0,0 +1,42 @@ +survey-ad { + background: white; + border-top: 1px solid @border-color; + bottom: 0; + position: absolute; + text-align: center; + transition: bottom @transition-speed ease; + width: 100%; + z-index: 90; + + a { + display: block; + line-height: 3rem; + width: 100%; + } + + .close { + background: none; + border: none; + cursor: pointer; + display: none; + font-size: 1.5rem; + line-height: 3rem; + margin: 0; + outline: none; + padding: 0; + position: absolute; + right: 0; + width: 3rem; + } + + &.hidden { + bottom: -3.1rem;// 3 rem + a little bit for the top border + } +} + +// Hide this component on mobile +@media screen and (max-width: @breakpoint) { + survey-ad { + display: none; + } +} diff --git a/templates/content.mustache b/templates/content.mustache index 07388e54..b32320f4 100644 --- a/templates/content.mustache +++ b/templates/content.mustache @@ -1,4 +1,4 @@ -
+
{{#with (getRoot)}} @@ -29,3 +29,4 @@
+{{> survey-ad.mustache}} diff --git a/templates/survey-ad.mustache b/templates/survey-ad.mustache new file mode 100644 index 00000000..2f3a2594 --- /dev/null +++ b/templates/survey-ad.mustache @@ -0,0 +1,8 @@ + + + + Help us improve CanJS by signing up for our community survey + + From 73a2ab598390421cb5030f500630f00016ffd8d4 Mon Sep 17 00:00:00 2001 From: Chasen Le Hara Date: Thu, 21 Sep 2017 17:24:01 -0700 Subject: [PATCH 2/2] Only show the survey-ad after the user has visited a few pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the user visits CanJS.com for the first time, we count any additional page loads (across sessions) as “engagements.” After three engagements, we show them the survey-ad. This shifts the control to showing unless dismissed to only showing if they have JS enabled and have interacted with the site a few times. --- static/canjs.js | 4 ++ static/survey-ad.js | 82 ++++++++++++++++++++++++++++++++------ static/survey-ad.less | 8 ++-- templates/content.mustache | 2 +- 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/static/canjs.js b/static/canjs.js index e48ffa94..bb4f2aa7 100644 --- a/static/canjs.js +++ b/static/canjs.js @@ -123,7 +123,11 @@ function init() { } if (!surveyAdControl) { + // Set up the survey ad control surveyAdControl = new SurveyAdControl("survey-ad"); + } else { + // Notify the survey ad control that the user loaded a page + surveyAdControl.didEngage(); } hasShownSearch = true; diff --git a/static/survey-ad.js b/static/survey-ad.js index 6794aaeb..c1f43be6 100644 --- a/static/survey-ad.js +++ b/static/survey-ad.js @@ -1,6 +1,13 @@ var Control = require("can-control"); module.exports = Control.extend({ + defaults: { + engagementCountKey: 'survey-ad-engagement-count', + engagementCountMinimum: 3, + userDidCloseKey: 'survey-ad-closed' + } +}, { + /* This should only happen once */ init: function() { var surveyAdElement = this.element; @@ -11,28 +18,77 @@ module.exports = Control.extend({ closeButton.style.display = 'inline-block'; } - // Look up the user’s preference in localStorage try { - var storageKey = 'survey-ad-closed'; - var didClose = window.localStorage.getItem(storageKey); - if (didClose) { - // Immediately hide this element - surveyAdElement.style.display = 'none'; - // .survey-ad-showing is used to change padding-bottom on the rest of the content - document.getElementById('everything').classList.remove('survey-ad-showing'); + + // Look up the user’s preference in localStorage + var didClose = window.localStorage.getItem(this.options.userDidCloseKey); + + // Get how many times the user has engaged + var engagementCount = window.localStorage.getItem(this.options.engagementCountKey); + + // If the user hasn’t closed the control and their engagement count is + // greater than the minimum, show the control + if (!didClose && engagementCount >= this.options.engagementCountMinimum) { + this.show(); } + } catch (error) { console.info('Caught localStorage error:', error); } }, - /* This event will fire when the user clicks the X button */ - '{element} .close click': function() { - // Add .hidden so the element animates out of view - this.element.classList.add('hidden'); + /* This should happen whenever a new page is loaded */ + didEngage: function() { + try { + var storageKey = this.options.engagementCountKey; + var engagementCount = parseInt(window.localStorage.getItem(storageKey) || '0', 10); + var newEngagementCount = 1 + engagementCount; + + // Store the new engagement count + window.localStorage.setItem(storageKey, newEngagementCount); + + // Potentially show the control + if (newEngagementCount >= this.options.engagementCountMinimum) { + + // Look up the user’s preference in localStorage + var didClose = window.localStorage.getItem(this.options.userDidCloseKey); + if (!didClose) { + this.show(); + } + + } + } catch (error) { + console.info('Caught localStorage error:', error); + } + }, + + hide: function(immediately) { + + // Remove .showing to animate the control out of view + if (this.element.classList) { + this.element.classList.remove('showing'); + } // .survey-ad-showing is used to change padding-bottom on the rest of the content document.getElementById('everything').classList.remove('survey-ad-showing'); + }, + + show: function() { + + // Add a class to animate the control into view + if (this.element.classList) { + this.element.classList.add('showing'); + } + + // .survey-ad-showing is used to change padding-bottom on the rest of the content + document.getElementById('everything').classList.add('survey-ad-showing'); + }, + + /* This event will fire when the user clicks the X button */ + '{element} .close click': function() { + + // Hide the control + this.hide(); // Try to remember the user’s preference in localStorage try { @@ -41,7 +97,7 @@ module.exports = Control.extend({ // certain amount of time or for a particular event, we can // more easily do that :) var currentTime = (new Date()).getTime(); - var storageKey = 'survey-ad-closed'; + var storageKey = this.options.userDidCloseKey; window.localStorage.setItem(storageKey, currentTime); } catch (error) { console.info('Caught localStorage error:', error); diff --git a/static/survey-ad.less b/static/survey-ad.less index 838d80e1..cbb6030a 100644 --- a/static/survey-ad.less +++ b/static/survey-ad.less @@ -1,12 +1,12 @@ survey-ad { background: white; border-top: 1px solid @border-color; - bottom: 0; + bottom: -3.1rem;// 3 rem + a little bit for the top border position: absolute; text-align: center; transition: bottom @transition-speed ease; width: 100%; - z-index: 90; + z-index: 90;// This matches #right’s z-index a { display: block; @@ -29,8 +29,8 @@ survey-ad { width: 3rem; } - &.hidden { - bottom: -3.1rem;// 3 rem + a little bit for the top border + &.showing { + bottom: 0; } } diff --git a/templates/content.mustache b/templates/content.mustache index b32320f4..9b481977 100644 --- a/templates/content.mustache +++ b/templates/content.mustache @@ -1,4 +1,4 @@ -
+
{{#with (getRoot)}}