commit
62c20ff5f2
|
@ -7,7 +7,7 @@ app/assets/javascripts/vendor.js
|
|||
app/assets/javascripts/locales/i18n.js
|
||||
app/assets/javascripts/defer/html-sanitizer-bundle.js
|
||||
app/assets/javascripts/discourse/lib/Markdown.Editor.js
|
||||
app/assets/javascripts/ember-addons
|
||||
app/assets/javascripts/ember-addons/
|
||||
jsapp/lib/Markdown.Editor.js
|
||||
lib/javascripts/locale/
|
||||
lib/javascripts/messageformat.js
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
export default Ember.Component.extend({
|
||||
action: "showCreateAccount",
|
||||
|
||||
actions: {
|
||||
neverShow() {
|
||||
this.keyValueStore.setItem('anon-cta-never', 't');
|
||||
this.session.set('showSignupCta', false);
|
||||
},
|
||||
hideForSession() {
|
||||
this.session.set('hideSignupCta', true);
|
||||
this.keyValueStore.setItem('anon-cta-hidden', new Date().getTime());
|
||||
Em.run.later(() =>
|
||||
this.session.set('showSignupCta', false),
|
||||
20 * 1000);
|
||||
},
|
||||
showCreateAccount() {
|
||||
this.sendAction();
|
||||
}
|
||||
},
|
||||
|
||||
signupMethodsTranslated: function() {
|
||||
const methods = Ember.get('Discourse.LoginMethod.all');
|
||||
const loginWithEmail = this.siteSettings.enable_local_logins;
|
||||
if (this.siteSettings.enable_sso) {
|
||||
return I18n.t('signup_cta.methods.sso');
|
||||
} else if (methods.length === 0) {
|
||||
if (loginWithEmail) {
|
||||
return I18n.t('signup_cta.methods.only_email');
|
||||
} else {
|
||||
return I18n.t('signup_cta.methods.unknown');
|
||||
}
|
||||
} else if (methods.length === 1) {
|
||||
let providerName = methods[0].name.capitalize();
|
||||
if (providerName === "Google_oauth2") {
|
||||
providerName = "Google";
|
||||
}
|
||||
if (loginWithEmail) {
|
||||
return I18n.t('signup_cta.methods.one_and_email', {provider: providerName});
|
||||
} else {
|
||||
return I18n.t('signup_cta.methods.only_other', {provider: providerName});
|
||||
}
|
||||
} else {
|
||||
if (loginWithEmail) {
|
||||
return I18n.t('signup_cta.methods.multiple', {count: methods.length});
|
||||
} else {
|
||||
return I18n.t('signup_cta.methods.multiple_no_email', {count: methods.length});
|
||||
}
|
||||
}
|
||||
}.property(),
|
||||
|
||||
_turnOffIfHidden: function() {
|
||||
if (this.session.get('hideSignupCta')) {
|
||||
this.session.set('showSignupCta', false);
|
||||
}
|
||||
}.on('willDestroyElement')
|
||||
});
|
|
@ -0,0 +1,90 @@
|
|||
import ScreenTrack from 'discourse/lib/screen-track';
|
||||
import Session from 'discourse/models/session';
|
||||
|
||||
const ANON_TOPIC_IDS = 5,
|
||||
ANON_PROMPT_READ_TIME = 15 * 60 * 1000,
|
||||
ANON_PROMPT_VISIT_COUNT = 2,
|
||||
ONE_DAY = 24 * 60 * 60 * 1000,
|
||||
PROMPT_HIDE_DURATION = ONE_DAY;
|
||||
|
||||
export default {
|
||||
name: "signup-cta",
|
||||
|
||||
initialize(container) {
|
||||
const screenTrack = ScreenTrack.current(),
|
||||
session = Session.current(),
|
||||
siteSettings = container.lookup('site-settings:main'),
|
||||
keyValueStore = container.lookup('key-value-store:main'),
|
||||
user = container.lookup('current-user:main');
|
||||
|
||||
screenTrack.set('keyValueStore', keyValueStore);
|
||||
|
||||
// Preconditions
|
||||
|
||||
if (user) return; // must not be logged in
|
||||
if (keyValueStore.get('anon-cta-never')) return; // "never show again"
|
||||
if (!siteSettings.allow_new_registrations) return;
|
||||
if (siteSettings.invite_only) return;
|
||||
if (siteSettings.must_approve_users) return;
|
||||
if (siteSettings.login_required) return;
|
||||
if (!siteSettings.enable_signup_cta) return;
|
||||
|
||||
function checkSignupCtaRequirements() {
|
||||
if (session.get('showSignupCta')) {
|
||||
return; // already shown
|
||||
}
|
||||
|
||||
if (session.get('hideSignupCta')) {
|
||||
return; // hidden for session
|
||||
}
|
||||
|
||||
if (keyValueStore.get('anon-cta-never')) {
|
||||
return; // hidden forever
|
||||
}
|
||||
|
||||
const now = new Date().getTime();
|
||||
const hiddenAt = keyValueStore.getInt('anon-cta-hidden', 0);
|
||||
if (hiddenAt > (now - PROMPT_HIDE_DURATION)) {
|
||||
return; // hidden in last 24 hours
|
||||
}
|
||||
|
||||
const visitCount = keyValueStore.getInt('anon-visit-count');
|
||||
if (visitCount < ANON_PROMPT_VISIT_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
const readTime = keyValueStore.getInt('anon-topic-time');
|
||||
if (readTime < ANON_PROMPT_READ_TIME) {
|
||||
return;
|
||||
}
|
||||
|
||||
const topicIdsString = keyValueStore.get('anon-topic-ids');
|
||||
if (!topicIdsString) { return; }
|
||||
let topicIdsAry = topicIdsString.split(',');
|
||||
if (topicIdsAry.length < ANON_TOPIC_IDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Requirements met.
|
||||
session.set('showSignupCta', true);
|
||||
}
|
||||
|
||||
screenTrack.set('anonFlushCallback', checkSignupCtaRequirements);
|
||||
|
||||
// Record a visit
|
||||
const nowVisit = new Date().getTime();
|
||||
const lastVisit = keyValueStore.getInt('anon-last-visit', 0);
|
||||
if (!lastVisit) {
|
||||
// First visit
|
||||
keyValueStore.setItem('anon-visit-count', 1);
|
||||
keyValueStore.setItem('anon-last-visit', nowVisit);
|
||||
} else if (nowVisit - lastVisit > ONE_DAY) {
|
||||
// More than a day
|
||||
const visitCount = keyValueStore.getInt('anon-visit-count', 1);
|
||||
keyValueStore.setItem('anon-visit-count', visitCount + 1);
|
||||
keyValueStore.setItem('anon-last-visit', nowVisit);
|
||||
}
|
||||
|
||||
checkSignupCtaRequirements();
|
||||
}
|
||||
};
|
|
@ -46,6 +46,14 @@ KeyValueStore.prototype = {
|
|||
return safeLocalStorage[this.context + key];
|
||||
},
|
||||
|
||||
getInt(key, def) {
|
||||
if (!def) { def = 0; }
|
||||
if (!safeLocalStorage) { return def; }
|
||||
const result = parseInt(this.get(key));
|
||||
if (!isFinite(result)) { return def; }
|
||||
return result;
|
||||
},
|
||||
|
||||
getObject(key) {
|
||||
if (!safeLocalStorage) { return null; }
|
||||
try {
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
import Singleton from 'discourse/mixins/singleton';
|
||||
|
||||
const PAUSE_UNLESS_SCROLLED = 1000 * 60 * 3,
|
||||
MAX_TRACKING_TIME = 1000 * 60 * 6;
|
||||
MAX_TRACKING_TIME = 1000 * 60 * 6,
|
||||
ANON_MAX_TOPIC_IDS = 5;
|
||||
|
||||
const ScreenTrack = Ember.Object.extend({
|
||||
|
||||
|
@ -85,9 +86,6 @@ const ScreenTrack = Ember.Object.extend({
|
|||
flush() {
|
||||
if (this.get('cancelled')) { return; }
|
||||
|
||||
// We don't log anything unless we're logged in
|
||||
if (!Discourse.User.current()) return;
|
||||
|
||||
const newTimings = {},
|
||||
totalTimings = this.get('totalTimings'),
|
||||
self = this;
|
||||
|
@ -118,26 +116,54 @@ const ScreenTrack = Ember.Object.extend({
|
|||
this.topicTrackingState.updateSeen(topicId, highestSeen);
|
||||
|
||||
if (!$.isEmptyObject(newTimings)) {
|
||||
Discourse.ajax('/topics/timings', {
|
||||
data: {
|
||||
timings: newTimings,
|
||||
topic_time: this.get('topicTime'),
|
||||
topic_id: topicId
|
||||
},
|
||||
cache: false,
|
||||
type: 'POST',
|
||||
headers: {
|
||||
'X-SILENCE-LOGGER': 'true'
|
||||
if (Discourse.User.current()) {
|
||||
Discourse.ajax('/topics/timings', {
|
||||
data: {
|
||||
timings: newTimings,
|
||||
topic_time: this.get('topicTime'),
|
||||
topic_id: topicId
|
||||
},
|
||||
cache: false,
|
||||
type: 'POST',
|
||||
headers: {
|
||||
'X-SILENCE-LOGGER': 'true'
|
||||
}
|
||||
}).then(function() {
|
||||
const controller = self.get('topicController');
|
||||
if (controller) {
|
||||
const postNumbers = Object.keys(newTimings).map(function(v) {
|
||||
return parseInt(v, 10);
|
||||
});
|
||||
controller.readPosts(topicId, postNumbers);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Anonymous viewer - save to localStorage
|
||||
const storage = this.get('keyValueStore');
|
||||
|
||||
// Save total time
|
||||
const existingTime = storage.getInt('anon-topic-time');
|
||||
storage.setItem('anon-topic-time', existingTime + this.get('topicTime'));
|
||||
|
||||
// Save unique topic IDs up to a max
|
||||
let topicIds = storage.get('anon-topic-ids');
|
||||
if (topicIds) {
|
||||
topicIds = topicIds.split(',').map(e => parseInt(e));
|
||||
} else {
|
||||
topicIds = [];
|
||||
}
|
||||
}).then(function(){
|
||||
const controller = self.get('topicController');
|
||||
if(controller){
|
||||
const postNumbers = Object.keys(newTimings).map(function(v){
|
||||
return parseInt(v,10);
|
||||
});
|
||||
controller.readPosts(topicId, postNumbers);
|
||||
if (topicIds.indexOf(topicId) === -1 && topicIds.length < ANON_MAX_TOPIC_IDS) {
|
||||
topicIds.push(topicId);
|
||||
storage.setItem('anon-topic-ids', topicIds.join(','));
|
||||
}
|
||||
});
|
||||
|
||||
// Inform the observer
|
||||
if (this.get('anonFlushCallback')) {
|
||||
this.get('anonFlushCallback')();
|
||||
}
|
||||
|
||||
// No need to call controller.readPosts()
|
||||
}
|
||||
|
||||
this.set('topicTime', 0);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<div class="signup-cta alert alert-info">
|
||||
{{#if session.hideSignupCta}}
|
||||
<p>
|
||||
{{i18n "signup_cta.hidden_for_session"}}
|
||||
<a {{action "neverShow"}}>{{i18n "signup_cta.hide_forever"}}</a>
|
||||
</p>
|
||||
{{else}}
|
||||
<p>{{i18n "signup_cta.intro"}}</p>
|
||||
<p>{{i18n "signup_cta.value_prop"}}</p>
|
||||
<p>{{signupMethodsTranslated}}</p>
|
||||
|
||||
<div class="buttons">
|
||||
{{d-button action="showCreateAccount" label="signup_cta.sign_up" icon="check" class="btn-primary"}}
|
||||
{{d-button action="hideForSession" label="signup_cta.hide_session" class="no-icon"}}
|
||||
<a {{action "neverShow"}}>{{i18n "signup_cta.hide_forever"}}</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
|
@ -86,7 +86,12 @@
|
|||
{{#if loadedAllPosts}}
|
||||
|
||||
{{view "topic-closing" topic=model}}
|
||||
{{view "topic-footer-buttons" topic=model}}
|
||||
{{#if session.showSignupCta}}
|
||||
{{! replace "Log In to Reply" with the infobox }}
|
||||
{{signup-cta}}
|
||||
{{else}}
|
||||
{{view "topic-footer-buttons" topic=model}}
|
||||
{{/if}}
|
||||
|
||||
{{#if model.pending_posts_count}}
|
||||
<div class="has-pending-posts">
|
||||
|
|
|
@ -964,6 +964,14 @@ span.highlighted {
|
|||
transition: opacity ease-out 1s;
|
||||
}
|
||||
|
||||
.signup-cta {
|
||||
margin-top: 15px;
|
||||
width: $topic-body-width;
|
||||
a {
|
||||
float: right;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet (portrait) ----------- */
|
||||
|
||||
|
|
|
@ -500,6 +500,21 @@ span.highlighted {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.signup-cta {
|
||||
margin-top: 15px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: calc(100% - 50px);
|
||||
a {
|
||||
float: right;
|
||||
text-decoration: underline;
|
||||
margin-top: 7px;
|
||||
}
|
||||
button {
|
||||
margin-right: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.small-action .small-action-desc {
|
||||
p {
|
||||
padding-top: 0;
|
||||
|
|
|
@ -722,6 +722,22 @@ en:
|
|||
one: reply
|
||||
other: replies
|
||||
|
||||
signup_cta:
|
||||
sign_up: "Sign Up"
|
||||
hide_session: "Remind me tomorrow"
|
||||
hide_forever: "Never show this again"
|
||||
hidden_for_session: "OK, I'll ask you tomorrow. You can always click the 'Log In' button to create an account, too."
|
||||
intro: Hey there! Looks like you're enjoying the forum, but you're not signed up for an account.
|
||||
value_prop: Logged-in users get their last read position in every topic saved, so you come right back where you left off. You can also "Watch" topics so that you get a notification whenever a new post is made, and you can give likes to others' posts.
|
||||
methods:
|
||||
sso: "Use your account on the main site to log in."
|
||||
only_email: "Signing up is easy: you just need a valid email account and a password."
|
||||
only_other: "Use your %{provider} account to sign up."
|
||||
one_and_email: "Use your %{provider} account, or an email and password, to sign up."
|
||||
multiple_no_email: "Choose from any of the %{count} supported login providers to get started."
|
||||
multiple: "Signing up couldn't be easier: use any of the %{count} available login providers, or sign up with an email and password."
|
||||
unknown: "error getting supported login methods"
|
||||
|
||||
summary:
|
||||
enabled_description: "You're viewing a summary of this topic: the most interesting posts as determined by the community."
|
||||
description: "There are <b>{{count}}</b> replies."
|
||||
|
|
|
@ -921,6 +921,7 @@ en:
|
|||
|
||||
enable_local_logins: "Enable local username and password login based accounts. (Note: this must be enabled for invites to work)"
|
||||
allow_new_registrations: "Allow new user registrations. Uncheck this to prevent anyone from creating a new account."
|
||||
enable_signup_cta: "Show a notice to returning anonymous users prompting them to sign up for an account."
|
||||
enable_yahoo_logins: "Enable Yahoo authentication"
|
||||
|
||||
enable_google_oauth2_logins: "Enable Google Oauth2 authentication. This is the method of authentication that Google currently supports. Requires key and secret."
|
||||
|
|
|
@ -202,6 +202,9 @@ login:
|
|||
allow_new_registrations:
|
||||
client: true
|
||||
default: true
|
||||
enable_signup_cta:
|
||||
client: true
|
||||
default: true
|
||||
enable_google_oauth2_logins:
|
||||
client: true
|
||||
default: false
|
||||
|
|
Loading…
Reference in New Issue