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:
parent
4ebebd12d3
commit
8ad11fe082
16 changed files with 43 additions and 384 deletions
13
Gruntfile.js
13
Gruntfile.js
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>';
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
}
|
1
core/server/public/members.min.js
vendored
1
core/server/public/members.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -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));
|
||||
|
||||
|
|
|
@ -115,6 +115,5 @@
|
|||
"adminFrameProtection": true,
|
||||
"sendWelcomeEmail": true,
|
||||
"stripeDirect": false,
|
||||
"enableStripePromoCodes": false,
|
||||
"portal": false
|
||||
"enableStripePromoCodes": false
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -17,6 +17,7 @@ const defaultSettingsKeys = [
|
|||
'description',
|
||||
'logo',
|
||||
'icon',
|
||||
'accent_color',
|
||||
'cover_image',
|
||||
'facebook',
|
||||
'twitter',
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -64,7 +64,8 @@ const defaultSettingsKeys = [
|
|||
'unsplash',
|
||||
'shared_views',
|
||||
'active_timezone',
|
||||
'default_locale'
|
||||
'default_locale',
|
||||
'accent_color'
|
||||
];
|
||||
|
||||
describe('Settings API (v3)', function () {
|
||||
|
|
Loading…
Reference in a new issue