2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

Enabled Portal (#12317)

no refs

[Portal](https://github.com/TryGhost/Portal) is a new drop-in script to make the bulk of Ghost membership features work on any theme out of the box, which was under a developer flag so far. This release removes the flag for Portal and makes it included as default for any members-enabled Ghost site. The Portal script is backward compatible with old public members script and existing Members-enabled themes should notice no change.

- Removes Portal config flag as Portal is now enabled by default
- Removes old members script as Portal is backward compatible with it
- Changes `{{content}}` helper to show default CTA in case of restricted content access
- `accent_color` setting is no more behind the dev experiment flag and included by default
- Adds migration to switch off Portal button setting for all existing sites which don't have Portal enabled in beta
This commit is contained in:
Rishabh Garg 2020-11-03 14:36:21 +05:30 committed by GitHub
parent 4ebebd12d3
commit 8ad11fe082
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 43 additions and 384 deletions

View file

@ -284,17 +284,6 @@ const configureGrunt = function (grunt) {
}
},
uglify: {
prod: {
options: {
sourceMap: false
},
files: {
'core/server/public/members.min.js': 'core/server/public/members.js'
}
}
},
postcss: {
prod: {
options: {
@ -548,7 +537,7 @@ const configureGrunt = function (grunt) {
//
// It is otherwise the same as running `grunt`, but is only used when running Ghost in the `production` env.
grunt.registerTask('prod', 'Build JS & templates for production',
['subgrunt:prod', 'uglify:prod', 'postcss:prod']);
['subgrunt:prod', 'postcss:prod']);
// ### Live reload
// `grunt dev` - build assets on the fly whilst developing

View file

@ -8,7 +8,7 @@
//
// Dev flag feature: In case of restricted content access for member-only posts, shows CTA box
const {templates, hbs, config, SafeString} = require('../services/proxy');
const {templates, hbs, SafeString} = require('../services/proxy');
const downsize = require('downsize');
const _ = require('lodash');
const createFrame = hbs.handlebars.createFrame;
@ -42,7 +42,7 @@ module.exports = function content(options = {}) {
this.html = '';
}
if (!this.access && (!!config.get('enableDeveloperExperiments') || !!config.get('portal'))) {
if (!_.isUndefined(this.access) && !this.access) {
return restrictedCta.apply(self, args);
}

View file

@ -8,7 +8,6 @@ const debug = require('ghost-ignition').debug('ghost_head');
const templateStyles = require('./tpl/styles');
const getMetaData = metaData.get;
const getAssetUrl = metaData.getAssetUrl;
function writeMetaTag(property, content, type) {
type = type || property.substring(0, 7) === 'twitter' ? 'name' : 'property';
@ -42,11 +41,8 @@ function getMembersHelper() {
const stripeDirectPublishableKey = settingsCache.get('stripe_publishable_key');
const stripeConnectAccountId = settingsCache.get('stripe_connect_account_id');
let membersHelper = `<script defer src="${getAssetUrl('public/members.js', true)}"></script>`;
if (config.get('enableDeveloperExperiments') || config.get('portal')) {
membersHelper = `<script defer src="https://unpkg.com/@tryghost/portal@latest/umd/portal.min.js" data-ghost="${urlUtils.getSiteUrl()}"></script>`;
membersHelper += (`<style type='text/css'> ${templateStyles}</style>`);
}
let membersHelper = `<script defer src="https://unpkg.com/@tryghost/portal@latest/umd/portal.min.js" data-ghost="${urlUtils.getSiteUrl()}"></script>`;
membersHelper += (`<style type='text/css'> ${templateStyles}</style>`);
if ((!!stripeDirectSecretKey && !!stripeDirectPublishableKey) || !!stripeConnectAccountId) {
membersHelper += '<script async src="https://js.stripe.com/v3/"></script>';
}

View file

@ -20,8 +20,7 @@ module.exports = {
clientExtensions: config.get('clientExtensions') || {},
enableDeveloperExperiments: config.get('enableDeveloperExperiments') || false,
stripeDirect: config.get('stripeDirect'),
mailgunIsConfigured: config.get('bulkEmail') && config.get('bulkEmail').mailgun,
portal: config.get('portal')
mailgunIsConfigured: config.get('bulkEmail') && config.get('bulkEmail').mailgun
};
if (billingUrl) {
response.billingUrl = billingUrl;

View file

@ -1,7 +1,6 @@
const ghostVersion = require('../../lib/ghost-version');
const settingsCache = require('../../services/settings/cache');
const urlUtils = require('../../../shared/url-utils');
const config = require('../../../shared/config');
const site = {
docName: 'site',
@ -18,11 +17,6 @@ const site = {
version: ghostVersion.safe
};
// accent_color is currently an experimental feature
if (!config.get('enableDeveloperExperiments') && !config.get('portal')) {
delete response.accent_color;
}
return response;
}
}

View file

@ -6,7 +6,6 @@ const gating = require('./post-gating');
const clean = require('./clean');
const extraAttrs = require('./extra-attrs');
const postsMetaSchema = require('../../../../../../data/schema').tables.posts_meta;
const config = require('../../../../../../../shared/config');
const mega = require('../../../../../../services/mega');
const mapUser = (model, frame) => {
@ -104,15 +103,8 @@ const mapSettings = (attrs, frame) => {
// fields completely.
if (_.isArray(attrs)) {
attrs = _.filter(attrs, (o) => {
if (o.key === 'accent_color' && !config.get('enableDeveloperExperiments') && !config.get('portal')) {
return false;
}
return o.key !== 'ghost_head' && o.key !== 'ghost_foot';
});
} else {
if (!config.get('enableDeveloperExperiments') && !config.get('portal')) {
delete attrs.accent_color;
}
}
return attrs;

View file

@ -0,0 +1,30 @@
const logging = require('../../../../../shared/logging');
const config = require('../../../../../shared/config');
module.exports = {
config: {
transaction: true
},
async up(options) {
// update portal button setting to false
const isPortalEnabled = config.get('portal');
if (!isPortalEnabled) {
logging.info(`Updating portal button setting to false`);
return await options
.transacting('settings')
.where('key', 'portal_button')
.update({
value: 'false'
});
}
logging.info(`Portal is enabled, ignoring portal button update`);
return Promise.resolve();
},
// `up` is only run to fix previously set default value for portal button,
// it doesn't make sense to be revert it back as `true` as feature is still behind dev flag
async down() {
return Promise.resolve();
}
};

View file

@ -1,320 +0,0 @@
/* eslint-disable no-var */
Array.prototype.forEach.call(document.querySelectorAll('form[data-members-form]'), function (form) {
var errorEl = form.querySelector('[data-members-error]');
function submitHandler(event) {
form.removeEventListener('submit', submitHandler);
event.preventDefault();
if (errorEl) {
errorEl.innerText = '';
}
form.classList.remove('success', 'invalid', 'error');
var emailInput = event.target.querySelector('input[data-members-email]');
var nameInput = event.target.querySelector('input[data-members-name]');
var email = emailInput.value;
var name = nameInput && nameInput.value || undefined;
var emailType = undefined;
var labels = [];
var labelInputs = event.target.querySelectorAll('input[data-members-label]') || [];
for (var i = 0;i < labelInputs.length; ++i) {
labels.push(labelInputs[i].value);
}
if (form.dataset.membersForm) {
emailType = form.dataset.membersForm;
}
form.classList.add('loading');
fetch('{{blog-url}}/members/api/send-magic-link/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
emailType: emailType,
labels: labels,
name: name
})
}).then(function (res) {
form.addEventListener('submit', submitHandler);
form.classList.remove('loading');
if (res.ok) {
form.classList.add('success')
} else {
if (errorEl) {
errorEl.innerText = 'There was an error sending the email, please try again';
}
form.classList.add('error')
}
});
}
form.addEventListener('submit', submitHandler);
});
Array.prototype.forEach.call(document.querySelectorAll('[data-members-plan]'), function (el) {
var errorEl = el.querySelector('[data-members-error]');
function clickHandler(event) {
el.removeEventListener('click', clickHandler);
event.preventDefault();
var plan = el.dataset.membersPlan;
var successUrl = el.dataset.membersSuccess;
var cancelUrl = el.dataset.membersCancel;
var checkoutSuccessUrl;
var checkoutCancelUrl;
if (successUrl) {
checkoutSuccessUrl = (new URL(successUrl, window.location.href)).href;
}
if (cancelUrl) {
checkoutCancelUrl = (new URL(cancelUrl, window.location.href)).href;
}
if (errorEl) {
errorEl.innerText = '';
}
el.classList.add('loading');
fetch('{{blog-url}}/members/api/session', {
credentials: 'same-origin'
}).then(function (res) {
if (!res.ok) {
return null;
}
return res.text();
}).then(function (identity) {
return fetch('{{blog-url}}/members/api/create-stripe-checkout-session/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
plan: plan,
identity: identity,
successUrl: checkoutSuccessUrl,
cancelUrl: checkoutCancelUrl
})
}).then(function (res) {
if (!res.ok) {
throw new Error('Could not create stripe checkout session');
}
return res.json();
});
}).then(function (result) {
var stripe = Stripe(result.publicKey);
return stripe.redirectToCheckout({
sessionId: result.sessionId
});
}).then(function (result) {
if (result.error) {
throw new Error(result.error.message);
}
}).catch(function (err) {
console.error(err);
el.addEventListener('click', clickHandler);
el.classList.remove('loading');
if (errorEl) {
errorEl.innerText = err.message;
}
el.classList.add('error');
});
}
el.addEventListener('click', clickHandler);
});
Array.prototype.forEach.call(document.querySelectorAll('[data-members-edit-billing]'), function (el) {
var errorEl = el.querySelector('[data-members-error]');
var membersSuccess = el.dataset.membersSuccess;
var membersCancel = el.dataset.membersCancel;
var successUrl;
var cancelUrl;
if (membersSuccess) {
successUrl = (new URL(membersSuccess, window.location.href)).href;
}
if (membersCancel) {
cancelUrl = (new URL(membersCancel, window.location.href)).href;
}
function clickHandler(event) {
el.removeEventListener('click', clickHandler);
event.preventDefault();
if (errorEl) {
errorEl.innerText = '';
}
el.classList.add('loading');
fetch('{{blog-url}}/members/api/session', {
credentials: 'same-origin'
}).then(function (res) {
if (!res.ok) {
return null;
}
return res.text();
}).then(function (identity) {
return fetch('{{blog-url}}/members/api/create-stripe-update-session/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
identity: identity,
successUrl: successUrl,
cancelUrl: cancelUrl
})
}).then(function (res) {
if (!res.ok) {
throw new Error('Could not create stripe checkout session');
}
return res.json();
});
}).then(function (result) {
var stripe = Stripe(result.publicKey);
return stripe.redirectToCheckout({
sessionId: result.sessionId
});
}).then(function (result) {
if (result.error) {
throw new Error(result.error.message);
}
}).catch(function (err) {
console.error(err);
el.addEventListener('click', clickHandler);
el.classList.remove('loading');
if (errorEl) {
errorEl.innerText = err.message;
}
el.classList.add('error');
});
}
el.addEventListener('click', clickHandler);
});
Array.prototype.forEach.call(document.querySelectorAll('[data-members-signout]'), function (el) {
function clickHandler(event) {
el.removeEventListener('click', clickHandler);
event.preventDefault();
el.classList.remove('error');
el.classList.add('loading');
fetch('{{blog-url}}/members/api/session', {
method: 'DELETE'
}).then(function (res) {
if (res.ok) {
window.location.reload();
} else {
el.addEventListener('click', clickHandler);
el.classList.remove('loading');
el.classList.add('error');
}
});
}
el.addEventListener('click', clickHandler);
});
Array.prototype.forEach.call(document.querySelectorAll('[data-members-cancel-subscription]'), function (el) {
var errorEl = el.parentElement.querySelector('[data-members-error]');
function clickHandler(event) {
el.removeEventListener('click', clickHandler);
event.preventDefault();
el.classList.remove('error');
el.classList.add('loading');
var subscriptionId = el.dataset.membersCancelSubscription;
if (errorEl) {
errorEl.innerText = '';
}
return fetch('{{blog-url}}/members/api/session', {
credentials: 'same-origin'
}).then(function (res) {
if (!res.ok) {
return null;
}
return res.text();
}).then(function (identity) {
return fetch('{{blog-url}}/members/api/subscriptions/' + subscriptionId + '/', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
identity: identity,
cancel_at_period_end: true
})
});
}).then(function (res) {
if (res.ok) {
window.location.reload();
} else {
el.addEventListener('click', clickHandler);
el.classList.remove('loading');
el.classList.add('error');
if (errorEl) {
errorEl.innerText = 'There was an error cancelling your subscription, please try again.';
}
}
});
}
el.addEventListener('click', clickHandler);
});
Array.prototype.forEach.call(document.querySelectorAll('[data-members-continue-subscription]'), function (el) {
var errorEl = el.parentElement.querySelector('[data-members-error]');
function clickHandler(event) {
el.removeEventListener('click', clickHandler);
event.preventDefault();
el.classList.remove('error');
el.classList.add('loading');
var subscriptionId = el.dataset.membersContinueSubscription;
if (errorEl) {
errorEl.innerText = '';
}
return fetch('{{blog-url}}/members/api/session', {
credentials: 'same-origin'
}).then(function (res) {
if (!res.ok) {
return null;
}
return res.text();
}).then(function (identity) {
return fetch('{{blog-url}}/members/api/subscriptions/' + subscriptionId + '/', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
identity: identity,
cancel_at_period_end: false
})
});
}).then(function (res) {
if (res.ok) {
window.location.reload();
} else {
el.addEventListener('click', clickHandler);
el.classList.remove('loading');
el.classList.add('error');
if (errorEl) {
errorEl.innerText = 'There was an error continuing your subscription, please try again.';
}
}
});
}
el.addEventListener('click', clickHandler);
});
var url = new URL(window.location);
if (url.searchParams.get('token')) {
url.searchParams.delete('token');
window.history.replaceState({}, document.title, url.href);
}

File diff suppressed because one or more lines are too long

View file

@ -96,14 +96,6 @@ module.exports = function setupSiteApp(options = {}) {
// Favicon
siteApp.use(mw.serveFavicon());
// /public/members.js
siteApp.get('/public/members.js', shared.middlewares.labs.members,
mw.servePublicFile('public/members.js', 'application/javascript', constants.ONE_YEAR_S));
// /public/members.min.js
siteApp.get('/public/members.min.js', shared.middlewares.labs.members,
mw.servePublicFile('public/members.min.js', 'application/javascript', constants.ONE_YEAR_S));
// Serve sitemap.xsl file
siteApp.use(mw.servePublicFile('sitemap.xsl', 'text/xsl', constants.ONE_DAY_S));

View file

@ -115,6 +115,5 @@
"adminFrameProtection": true,
"sendWelcomeEmail": true,
"stripeDirect": false,
"enableStripePromoCodes": false,
"portal": false
"enableStripePromoCodes": false
}

View file

@ -24,7 +24,7 @@ const expectedProperties = {
action: ['id', 'resource_type', 'actor_type', 'event', 'created_at', 'actor'],
config: ['version', 'environment', 'database', 'mail', 'labs', 'clientExtensions', 'enableDeveloperExperiments', 'useGravatar', 'stripeDirect', 'portal'],
config: ['version', 'environment', 'database', 'mail', 'labs', 'clientExtensions', 'enableDeveloperExperiments', 'useGravatar', 'stripeDirect'],
post: _(schema.posts)
.keys()

View file

@ -17,6 +17,7 @@ const defaultSettingsKeys = [
'description',
'logo',
'icon',
'accent_color',
'cover_image',
'facebook',
'twitter',

View file

@ -37,13 +37,6 @@ describe('Basic Members Routes', function () {
sinon.restore();
});
describe('Static files', function () {
it('should serve members.js file', function () {
return request.get('/public/members.js')
.expect(200);
});
});
describe('Routes', function () {
it('should error serving webhook endpoint without any parameters', function () {
return request.post('/members/webhooks/stripe')
@ -121,13 +114,6 @@ describe('Basic Members Routes', function () {
sinon.restore();
});
describe('Static files', function () {
it('should not serve members js file', function () {
return request.get('/public/members.js')
.expect(404);
});
});
describe('Routes', function () {
it('should not serve webhook endpoint', function () {
return request.post('/members/webhooks/stripe')

View file

@ -64,7 +64,8 @@ const defaultSettingsKeyTypes = [
{key: 'unsplash', type: 'blog'},
{key: 'shared_views', type: 'blog'},
{key: 'active_timezone', type: 'blog'},
{key: 'default_locale', type: 'blog'}
{key: 'default_locale', type: 'blog'},
{key: 'accent_color', type: 'blog'}
];
describe('Settings API (canary)', function () {

View file

@ -64,7 +64,8 @@ const defaultSettingsKeys = [
'unsplash',
'shared_views',
'active_timezone',
'default_locale'
'default_locale',
'accent_color'
];
describe('Settings API (v3)', function () {