mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Added members lib module (#10260)
* Added members library inc. gateway refs #10213 * Added the auth pages and build steps for them refs #10213 * Cleaned up logs * Updated gruntfile to run yarn for member auth * Design refinements on members popups * UI refinements * Updated backend call to trigger only if frontend validation passes * Design refinements for error messages * Added error message for email failure * Updated request-password-reset to not attempt to send headers twice * Updated preact publicPath to relative path * Build auth pages on init
This commit is contained in:
parent
e511fcf4d9
commit
b219e26ea6
23 changed files with 11469 additions and 0 deletions
51
ghost/members-api/cookies.js
Normal file
51
ghost/members-api/cookies.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
const crypto = require('crypto');
|
||||
const cookie = require('cookie');
|
||||
|
||||
const MAX_AGE = 60 * 60 * 24 * 184;
|
||||
|
||||
module.exports = function cookies(sessionSecret) {
|
||||
function encodeCookie(data) {
|
||||
const encodedData = encodeURIComponent(data);
|
||||
const hmac = crypto.createHmac('sha256', sessionSecret);
|
||||
hmac.update(encodedData);
|
||||
return `${hmac.digest('hex')}~${encodedData}`;
|
||||
}
|
||||
|
||||
function decodeCookie(data) {
|
||||
const hmac = crypto.createHmac('sha256', sessionSecret);
|
||||
const [sentHmac, sentData] = data.split('~');
|
||||
if (hmac.update(sentData).digest('hex') !== sentHmac) {
|
||||
return null;
|
||||
}
|
||||
return decodeURIComponent(sentData);
|
||||
}
|
||||
|
||||
function setCookie(member) {
|
||||
return cookie.serialize('signedin', member.id, {
|
||||
maxAge: MAX_AGE,
|
||||
path: '/ghost/api/v2/members/token',
|
||||
httpOnly: true,
|
||||
encode: encodeCookie
|
||||
});
|
||||
}
|
||||
|
||||
function removeCookie() {
|
||||
return cookie.serialize('signedin', false, {
|
||||
maxAge: 0,
|
||||
path: '/ghost/api/v2/members/token',
|
||||
httpOnly: true
|
||||
});
|
||||
}
|
||||
|
||||
function getCookie(req) {
|
||||
return cookie.parse(req.headers.cookie || '', {
|
||||
decode: decodeCookie
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
setCookie,
|
||||
removeCookie,
|
||||
getCookie
|
||||
};
|
||||
};
|
206
ghost/members-api/index.js
Normal file
206
ghost/members-api/index.js
Normal file
|
@ -0,0 +1,206 @@
|
|||
const jose = require('node-jose');
|
||||
const {Router, static} = require('express');
|
||||
const body = require('body-parser');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const cookies = require('./cookies');
|
||||
|
||||
module.exports = function MembersApi({
|
||||
config: {
|
||||
issuer,
|
||||
privateKey,
|
||||
publicKey,
|
||||
sessionSecret,
|
||||
ssoOrigin
|
||||
},
|
||||
validateAudience,
|
||||
createMember,
|
||||
validateMember,
|
||||
updateMember,
|
||||
getMember,
|
||||
sendEmail
|
||||
}) {
|
||||
const keyStore = jose.JWK.createKeyStore();
|
||||
const keyStoreReady = keyStore.add(privateKey, 'pem');
|
||||
|
||||
const router = Router();
|
||||
|
||||
const apiRouter = Router();
|
||||
|
||||
apiRouter.use(body.json());
|
||||
apiRouter.use(function waitForKeyStore(req, res, next) {
|
||||
keyStoreReady.then((jwk) => {
|
||||
req.jwk = jwk;
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
const {getCookie, setCookie, removeCookie} = cookies(sessionSecret);
|
||||
|
||||
apiRouter.post('/token', getData('audience'), (req, res) => {
|
||||
const {signedin} = getCookie(req);
|
||||
if (!signedin) {
|
||||
res.writeHead(401, {
|
||||
'Set-Cookie': removeCookie()
|
||||
});
|
||||
return res.end();
|
||||
}
|
||||
|
||||
const {audience, origin} = req.data;
|
||||
|
||||
validateAudience({audience, origin, id: signedin}).then(() => {
|
||||
const token = jwt.sign({
|
||||
sub: signedin,
|
||||
kid: req.jwk.kid
|
||||
}, privateKey, {
|
||||
algorithm: 'RS512',
|
||||
audience,
|
||||
issuer
|
||||
});
|
||||
return res.end(token);
|
||||
}).catch(handleError(403, res));
|
||||
});
|
||||
|
||||
function ssoOriginCheck(req, res, next) {
|
||||
if (!req.data.origin || req.data.origin !== ssoOrigin) {
|
||||
res.writeHead(403);
|
||||
return res.end();
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
apiRouter.post('/request-password-reset', getData('email'), ssoOriginCheck, (req, res) => {
|
||||
const {email} = req.data;
|
||||
|
||||
const memberPromise = getMember({email});
|
||||
|
||||
memberPromise.catch(() => {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
});
|
||||
|
||||
memberPromise.then((member) => {
|
||||
const token = jwt.sign({
|
||||
sub: member.id,
|
||||
kid: req.jwk.kid
|
||||
}, privateKey, {
|
||||
algorithm: 'RS512',
|
||||
issuer
|
||||
});
|
||||
return sendEmail(member, {token});
|
||||
}).then(() => {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
}).catch(handleError(500, res));
|
||||
});
|
||||
|
||||
apiRouter.post('/reset-password', getData('token', 'password'), ssoOriginCheck, (req, res) => {
|
||||
const {token, password} = req.data;
|
||||
|
||||
try {
|
||||
jwt.verify(token, publicKey, {
|
||||
algorithm: 'RS512',
|
||||
issuer
|
||||
});
|
||||
} catch (err) {
|
||||
res.writeHead(401);
|
||||
return res.end();
|
||||
}
|
||||
|
||||
const id = jwt.decode(token).sub;
|
||||
|
||||
updateMember({id}, {password}).then((member) => {
|
||||
res.writeHead(200, {
|
||||
'Set-Cookie': setCookie(member)
|
||||
});
|
||||
res.end();
|
||||
}).catch(handleError(401, res));
|
||||
});
|
||||
|
||||
apiRouter.post('/signup', getData('name', 'email', 'password'), ssoOriginCheck, (req, res) => {
|
||||
const {name, email, password} = req.data;
|
||||
|
||||
// @TODO this should attempt to reset password before creating member
|
||||
createMember({name, email, password}).then((member) => {
|
||||
res.writeHead(200, {
|
||||
'Set-Cookie': setCookie(member)
|
||||
});
|
||||
res.end();
|
||||
}).catch(handleError(400, res));
|
||||
});
|
||||
|
||||
apiRouter.post('/signin', getData('email', 'password'), ssoOriginCheck, (req, res) => {
|
||||
const {email, password} = req.data;
|
||||
|
||||
validateMember({email, password}).then((member) => {
|
||||
res.writeHead(200, {
|
||||
'Set-Cookie': setCookie(member)
|
||||
});
|
||||
res.end();
|
||||
}).catch(handleError(401, res));
|
||||
});
|
||||
|
||||
apiRouter.post('/signout', getData(), ssoOriginCheck, (req, res) => {
|
||||
res.writeHead(200, {
|
||||
'Set-Cookie': removeCookie()
|
||||
});
|
||||
res.end();
|
||||
});
|
||||
|
||||
const staticRouter = Router();
|
||||
staticRouter.use('/static', static(require('path').join(__dirname, './static/auth/dist')));
|
||||
staticRouter.use('/gateway', static(require('path').join(__dirname, './static/gateway')));
|
||||
staticRouter.get('/*', (req, res) => {
|
||||
res.sendFile(require('path').join(__dirname, './static/auth/dist/index.html'));
|
||||
});
|
||||
|
||||
router.use('/api', apiRouter);
|
||||
router.use('/static', staticRouter);
|
||||
router.get('/.well-known/jwks.json', (req, res) => {
|
||||
keyStoreReady.then(() => {
|
||||
res.json(keyStore.toJSON());
|
||||
});
|
||||
});
|
||||
|
||||
function httpHandler(req, res, next) {
|
||||
return router.handle(req, res, next);
|
||||
}
|
||||
|
||||
httpHandler.staticRouter = staticRouter;
|
||||
httpHandler.apiRouter = apiRouter;
|
||||
httpHandler.keyStore = keyStore;
|
||||
|
||||
return httpHandler;
|
||||
};
|
||||
|
||||
function getData(...props) {
|
||||
return function (req, res, next) {
|
||||
if (!req.body) {
|
||||
res.writeHead(400);
|
||||
return res.end();
|
||||
}
|
||||
|
||||
const data = props.concat('origin').reduce((data, prop) => {
|
||||
if (!data || !req.body[prop]) {
|
||||
return null;
|
||||
}
|
||||
return Object.assign(data, {
|
||||
[prop]: req.body[prop]
|
||||
});
|
||||
}, {});
|
||||
|
||||
if (!data) {
|
||||
res.writeHead(400);
|
||||
return res.end(`Expected {${props.join(', ')}}`);
|
||||
}
|
||||
req.data = data || {};
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function handleError(status, res) {
|
||||
return function () {
|
||||
res.writeHead(status);
|
||||
res.end();
|
||||
};
|
||||
}
|
4
ghost/members-api/static/auth/.gitignore
vendored
Normal file
4
ghost/members-api/static/auth/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
/dist
|
||||
/build
|
||||
/*.log
|
47
ghost/members-api/static/auth/Gruntfile.js
Normal file
47
ghost/members-api/static/auth/Gruntfile.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* eslint-env node */
|
||||
/* eslint-disable object-shorthand */
|
||||
'use strict';
|
||||
|
||||
module.exports = function (grunt) {
|
||||
// Find all of the task which start with `grunt-` and load them, rather than explicitly declaring them all
|
||||
require('matchdep').filterDev(['grunt-*', '!grunt-cli']).forEach(grunt.loadNpmTasks);
|
||||
|
||||
grunt.initConfig({
|
||||
clean: {
|
||||
built: {
|
||||
src: ['dist/**']
|
||||
},
|
||||
dependencies: {
|
||||
src: ['node_modules/**']
|
||||
},
|
||||
tmp: {
|
||||
src: ['tmp/**']
|
||||
}
|
||||
},
|
||||
|
||||
shell: {
|
||||
'npm-install': {
|
||||
command: 'yarn install'
|
||||
},
|
||||
|
||||
preact: {
|
||||
command: function (mode) {
|
||||
switch (mode) {
|
||||
case 'prod':
|
||||
return 'yarn build';
|
||||
case 'dev':
|
||||
return 'yarn dev';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('init', 'Install the preact member dependencies',
|
||||
['shell:npm-install', 'shell:preact:prod']
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Brand/Ghost Logotype - Light</title><g transform="translate(0 .285)" fill-rule="nonzero" fill="#FFF" opacity=".6"><rect x=".049" y="15.43" width="7.901" height="3.804" rx="1.902"/><rect x="11.9" y="15.43" width="7.896" height="3.804" rx="1.902"/><rect x=".043" y="7.822" width="19.755" height="3.804" rx="1.902"/><path d="M1.95.216H10c1.05 0 1.901.85 1.901 1.901 0 1.05-.851 1.902-1.901 1.902H1.95c-1.05 0-1.9-.851-1.9-1.902C.05 1.067.9.216 1.95.216zM17.752.216h.147c1.05 0 1.902.85 1.902 1.901 0 1.05-.852 1.902-1.902 1.902h-.147c-1.05 0-1.901-.851-1.901-1.902 0-1.05.851-1.901 1.901-1.901z"/></g></svg>
|
After Width: | Height: | Size: 694 B |
|
@ -0,0 +1 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><title>icon-email</title><g fill="none" fill-rule="evenodd"><path fill-opacity="0" fill="#FFF" fill-rule="nonzero" d="M0 0h16v16H0z"/><path d="M15.619 12.53c0 .646-.38.97-1.143.97H1.524c-.762 0-1.143-.324-1.143-.97V3.47c0-.267.112-.496.335-.686.223-.19.492-.284.808-.284h12.952c.762 0 1.143.324 1.143.97v9.06z" stroke="#B2C2C9" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#B2C2C9" stroke-linecap="round" stroke-linejoin="round" d="M15.238 3L8 10 .762 3"/></g></svg>
|
After Width: | Height: | Size: 566 B |
|
@ -0,0 +1 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><title>icon-lock</title><g fill="none" fill-rule="evenodd"><path d="M8.75 10.25c0 .5-.25.75-.75.75s-.75-.25-.75-.75.25-.75.75-.75.75.25.75.75zM8 11v2.25" stroke="#B2C2C9" stroke-linecap="round" stroke-linejoin="round"/><path d="M2.706 6.5h10.588c.47 0 .706.214.706.643v7.714c0 .429-.235.643-.706.643H2.706c-.47 0-.706-.214-.706-.643V7.143c0-.429.235-.643.706-.643zM3.875 4.817c0-1.645.687-2.878 2.063-3.7 1.375-.823 2.75-.823 4.125 0 1.374.822 2.062 2.055 2.062 3.7V6.5h-8.25V4.817z" stroke="#B2C2C9" stroke-linecap="round" stroke-linejoin="round"/></g></svg>
|
After Width: | Height: | Size: 642 B |
|
@ -0,0 +1 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><title>icon-name</title><g fill="none" fill-rule="evenodd"><path stroke-opacity=".012" stroke="#000" stroke-width="0" d="M.5.5h15v15H.5z"/><g stroke="#B2C2C9" stroke-linecap="round" stroke-linejoin="round"><path d="M15.187 8c0 1.985-.701 3.679-2.105 5.082C11.68 14.486 9.985 15.187 8 15.187c-1.985 0-3.679-.701-5.082-2.105C1.514 11.68.812 9.985.812 8c0-1.985.702-3.679 2.106-5.082C4.32 1.514 6.015.812 8 .812c1.985 0 3.679.702 5.082 2.106C14.486 4.32 15.187 6.015 15.187 8z"/><path d="M2.974 13.138c1.071-.62 2.199-1.11 3.383-1.47.524-.193.58-1.393.205-1.805-.54-.596-1-1.294-1-2.98-.066-.711.145-1.328.633-1.85.489-.522 1.09-.773 1.805-.754.715-.02 1.316.232 1.805.754.488.522.7 1.139.632 1.85 0 1.688-.458 2.384-1 2.98-.375.412-.318 1.612.205 1.805 1.185.36 2.313.85 3.384 1.47"/></g></g></svg>
|
After Width: | Height: | Size: 879 B |
20
ghost/members-api/static/auth/components/icons.js
Normal file
20
ghost/members-api/static/auth/components/icons.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
export const IconEmail = (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><title>icon-email</title><g fill="none" fill-rule="evenodd"><path d="M15.619 12.53c0 .646-.38.97-1.143.97H1.524c-.762 0-1.143-.324-1.143-.97V3.47c0-.267.112-.496.335-.686.223-.19.492-.284.808-.284h12.952c.762 0 1.143.324 1.143.97v9.06z" stroke="#B2C2C9" stroke-linecap="round" stroke-linejoin="round" /><path stroke="#B2C2C9" stroke-linecap="round" stroke-linejoin="round" d="M15.238 3L8 10 .762 3" /></g></svg>
|
||||
);
|
||||
|
||||
export const IconLock = (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><title>icon-lock</title><g fill="none" fill-rule="evenodd"><path d="M8.75 10.25c0 .5-.25.75-.75.75s-.75-.25-.75-.75.25-.75.75-.75.75.25.75.75zM8 11v2.25" stroke="#B2C2C9" stroke-linecap="round" stroke-linejoin="round" /><path d="M2.706 6.5h10.588c.47 0 .706.214.706.643v7.714c0 .429-.235.643-.706.643H2.706c-.47 0-.706-.214-.706-.643V7.143c0-.429.235-.643.706-.643zM3.875 4.817c0-1.645.687-2.878 2.063-3.7 1.375-.823 2.75-.823 4.125 0 1.374.822 2.062 2.055 2.062 3.7V6.5h-8.25V4.817z" stroke="#B2C2C9" stroke-linecap="round" stroke-linejoin="round" /></g></svg>
|
||||
);
|
||||
|
||||
export const IconName = (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><title>icon-name</title><g fill="none" fill-rule="evenodd"><path stroke-opacity=".012" stroke="#000" stroke-width="0" d="M.5.5h15v15H.5z" /><g stroke="#B2C2C9" stroke-linecap="round" stroke-linejoin="round"><path d="M15.187 8c0 1.985-.701 3.679-2.105 5.082C11.68 14.486 9.985 15.187 8 15.187c-1.985 0-3.679-.701-5.082-2.105C1.514 11.68.812 9.985.812 8c0-1.985.702-3.679 2.106-5.082C4.32 1.514 6.015.812 8 .812c1.985 0 3.679.702 5.082 2.106C14.486 4.32 15.187 6.015 15.187 8z" /><path d="M2.974 13.138c1.071-.62 2.199-1.11 3.383-1.47.524-.193.58-1.393.205-1.805-.54-.596-1-1.294-1-2.98-.066-.711.145-1.328.633-1.85.489-.522 1.09-.773 1.805-.754.715-.02 1.316.232 1.805.754.488.522.7 1.139.632 1.85 0 1.688-.458 2.384-1 2.98-.375.412-.318 1.612.205 1.805 1.185.36 2.313.85 3.384 1.47" /></g></g></svg>
|
||||
);
|
||||
|
||||
export const IconClose = (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><title>icon-close</title><g fill="none" fill-rule="evenodd"><path d="M2.25 2.25l11.5 11.5M13.75 2.25l-11.5 11.5" stroke="#9BAEB8" stroke-linecap="round" stroke-linejoin="round" /></g></svg>
|
||||
);
|
||||
|
||||
|
||||
export const IconError = (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><title>icon-error</title><g fill="none" fill-rule="evenodd"><path d="M15 7.88c.005 1.944-.674 3.61-2.038 4.997C11.6 14.263 9.945 14.97 8 14.999c-1.921.029-3.567-.63-4.937-1.976S1.005 10.043 1 8.123c-.005-1.946.674-3.612 2.037-4.999C4.401 1.737 6.055 1.029 8 1.001c1.921-.029 3.567.63 4.938 1.977 1.37 1.347 2.057 2.98 2.062 4.902zM7.933 9.337V4.67" stroke="#F05230" stroke-linecap="round" stroke-linejoin="round" /><path d="M7.927 11.67c-.046 0-.084.018-.116.051-.031.033-.046.073-.044.119.004.109.06.163.168.163.046 0 .085-.018.116-.051.032-.033.047-.073.045-.119-.003-.105-.057-.16-.163-.163H7.93" stroke="#F05230" stroke-linecap="round" stroke-linejoin="round" /></g></svg>
|
||||
);
|
395
ghost/members-api/static/auth/index.js
Normal file
395
ghost/members-api/static/auth/index.js
Normal file
|
@ -0,0 +1,395 @@
|
|||
import './styles/members.css';
|
||||
import {IconEmail, IconLock, IconName, IconClose, IconError} from './components/icons';
|
||||
import { Component } from 'preact';
|
||||
const origin = new URL(window.location).origin;
|
||||
const membersApi = location.pathname.replace(/\/members\/auth\/?$/, '/ghost/api/v2/members');
|
||||
const storage = window.localStorage;
|
||||
var layer0 = require('./layer0');
|
||||
|
||||
function getFreshState() {
|
||||
const [hash, formType, query] = window.location.hash.match(/^#([^?]+)\??(.*)$/) || ['#signin?', 'signin', ''];
|
||||
return {
|
||||
formData: {},
|
||||
query,
|
||||
formType,
|
||||
showError: false,
|
||||
submitFail: false
|
||||
};
|
||||
}
|
||||
|
||||
export default class App extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = getFreshState();
|
||||
this.gatewayFrame = '';
|
||||
window.addEventListener("hashchange", () => this.onHashChange(), false);
|
||||
}
|
||||
|
||||
loadGateway() {
|
||||
const blogUrl = window.location.href.substring(0, window.location.href.indexOf('/members/auth'));
|
||||
const frame = window.document.createElement('iframe');
|
||||
frame.id = 'member-gateway';
|
||||
frame.style.display = 'none';
|
||||
frame.src = `${blogUrl}/members/gateway`;
|
||||
frame.onload = () => {
|
||||
this.gatewayFrame = layer0(frame);
|
||||
};
|
||||
document.body.appendChild(frame);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadGateway();
|
||||
}
|
||||
|
||||
onHashChange() {
|
||||
this.setState(getFreshState());
|
||||
}
|
||||
|
||||
onInputChange(e, name) {
|
||||
let value = e.target.value;
|
||||
this.setState({
|
||||
formData: {
|
||||
...this.state.formData,
|
||||
[name]: value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
submitForm(e) {
|
||||
e.preventDefault();
|
||||
if (this.hasFrontendError(this.state.formType)) {
|
||||
return false;
|
||||
}
|
||||
switch (this.state.formType) {
|
||||
case 'signin':
|
||||
this.signin(this.state.formData);
|
||||
break;
|
||||
case 'signup':
|
||||
this.signup(this.state.formData);
|
||||
break;
|
||||
case 'request-password-reset':
|
||||
this.requestPasswordReset(this.state.formData);
|
||||
break;
|
||||
case 'password-reset-sent':
|
||||
this.resendPasswordResetEmail(this.state.formData)
|
||||
break;
|
||||
case 'reset-password':
|
||||
this.resetPassword(this.state.formData)
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
signin({ email, password }) {
|
||||
this.gatewayFrame.call('signin', {email, password}, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
this.setState({
|
||||
submitFail: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
signup({ name, email, password }) {
|
||||
this.gatewayFrame.call('signup', { name, email, password }, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
this.setState({
|
||||
submitFail: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
requestPasswordReset({ email }) {
|
||||
this.gatewayFrame.call('request-password-reset', {email}, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
this.setState({
|
||||
submitFail: true
|
||||
});
|
||||
} else {
|
||||
window.location.hash = 'password-reset-sent';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resendPasswordResetEmail({ email }) {
|
||||
this.gatewayFrame.call('request-password-reset', {email}, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
this.setState({
|
||||
submitFail: true
|
||||
});
|
||||
} else {
|
||||
window.location.hash = 'password-reset-sent';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resetPassword({ password }) {
|
||||
const queryParams = new URLSearchParams(this.state.query);
|
||||
const token = queryParams.get('token') || '';
|
||||
this.gatewayFrame.call('reset-password', {password, token}, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
this.setState({
|
||||
submitFail: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hasFrontendError(formType = this.state.formType) {
|
||||
switch(formType) {
|
||||
case 'signin':
|
||||
return (
|
||||
this.hasError({errorType: 'no-input', data: 'email'}) ||
|
||||
this.hasError({errorType: 'no-input', data: 'password'})
|
||||
);
|
||||
case 'signup':
|
||||
return (
|
||||
this.hasError({errorType: 'no-input', data: 'email'}) ||
|
||||
this.hasError({errorType: 'no-input', data: 'password'}) ||
|
||||
this.hasError({errorType: 'no-input', data: 'name'})
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasError({errorType, data}) {
|
||||
if (!this.state.showError) {
|
||||
return false;
|
||||
}
|
||||
let value = '';
|
||||
switch(errorType) {
|
||||
case 'no-input':
|
||||
value = this.state.formData[data];
|
||||
return (!value);
|
||||
case 'form-submit':
|
||||
return this.state.submitFail;
|
||||
}
|
||||
}
|
||||
|
||||
renderError({error, formType}) {
|
||||
if (this.hasError(error)) {
|
||||
let errorLabel = '';
|
||||
switch(error.errorType) {
|
||||
case 'no-input':
|
||||
errorLabel = `Enter ${error.data}`;
|
||||
break;
|
||||
case 'form-submit':
|
||||
switch(formType) {
|
||||
case 'signin':
|
||||
errorLabel = "Wrong email or password";
|
||||
break;
|
||||
case 'signup':
|
||||
errorLabel = "Email already registered"
|
||||
break;
|
||||
case 'request-password-reset':
|
||||
errorLabel = "Unable to send email"
|
||||
break;
|
||||
case 'password-reset-sent':
|
||||
errorLabel = "Unable to send email"
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<span>{ errorLabel }</span>
|
||||
)
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderFormHeaders(formType) {
|
||||
let mainTitle = '';
|
||||
let ctaTitle = '';
|
||||
let ctaLabel = '';
|
||||
let hash = '';
|
||||
switch (formType) {
|
||||
case 'signup':
|
||||
mainTitle = 'Sign Up';
|
||||
ctaTitle = 'Already a member?';
|
||||
ctaLabel = 'Log in';
|
||||
hash = 'signin';
|
||||
break;
|
||||
case 'signin':
|
||||
mainTitle = 'Log In';
|
||||
ctaTitle = 'Not a member?';
|
||||
ctaLabel = 'Sign up';
|
||||
hash = 'signup';
|
||||
break;
|
||||
case 'request-password-reset':
|
||||
mainTitle = 'Reset password';
|
||||
ctaTitle = '';
|
||||
ctaLabel = 'Log in';
|
||||
hash = 'signin';
|
||||
break;
|
||||
case 'password-reset-sent':
|
||||
mainTitle = 'Reset password';
|
||||
ctaTitle = '';
|
||||
ctaLabel = 'Log in';
|
||||
hash = 'signin';
|
||||
break;
|
||||
case 'reset-password':
|
||||
mainTitle = 'Reset password';
|
||||
ctaTitle = '';
|
||||
ctaLabel = 'Log in';
|
||||
hash = 'signin';
|
||||
break;
|
||||
}
|
||||
let formError = this.renderError({ error: {errorType: "form-submit"}, formType });
|
||||
return (
|
||||
<div className="flex flex-column">
|
||||
<div className="gm-logo"></div>
|
||||
<div className="gm-auth-header">
|
||||
<h1>{ mainTitle }</h1>
|
||||
<div className="flex items-baseline">
|
||||
<h4>{ ctaTitle }</h4>
|
||||
<a href="javascript:;"
|
||||
onClick={(e) => {window.location.hash = hash}}
|
||||
>
|
||||
{ctaLabel}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{(formError ? <div class="gm-form-errortext"><i>{ IconError }</i> { formError }</div> : "")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFormInput({type, name, label, icon, placeholder, required, formType}) {
|
||||
let value = this.state.formData[name];
|
||||
let className = "";
|
||||
let forgot = (type === 'password' && formType === 'signin');
|
||||
let inputError = this.renderError({ error: {errorType: 'no-input', data: name}, formType });
|
||||
className += (value ? "gm-input-filled" : "") + (forgot ? " gm-forgot-input" : "") + (inputError ? " gm-error" : "");
|
||||
|
||||
return (
|
||||
<div className="mt8">
|
||||
<div className="gm-floating-input">
|
||||
<input
|
||||
type={ type }
|
||||
name={ name }
|
||||
key={ name }
|
||||
placeholder={ placeholder }
|
||||
value={ value || '' }
|
||||
onInput={ (e) => this.onInputChange(e, name) }
|
||||
required = {required}
|
||||
className={ className }
|
||||
/>
|
||||
<label for={ name }> { label }</label>
|
||||
<i>{ icon }</i>
|
||||
{ (forgot ? <a href="javascript:;" className="gm-forgot-link" onClick={(e) => {window.location.hash = 'request-password-reset'}}>Forgot</a> : "") }
|
||||
</div>
|
||||
<div class="gm-input-errortext">{ inputError }</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFormText({formType}) {
|
||||
return (
|
||||
<div className="mt8">
|
||||
<p>We’ve sent a recovery email to your inbox. Follow the link in the email to reset your password.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
onSubmitClick(e) {
|
||||
this.setState({
|
||||
showError: true,
|
||||
submitFail: false
|
||||
});
|
||||
}
|
||||
|
||||
renderFormSubmit({buttonLabel, formType}) {
|
||||
return (
|
||||
<div className="mt8">
|
||||
<button type="submit" name={ formType } className="gm-btn-blue" onClick={(e) => this.onSubmitClick(e)}>{ buttonLabel }</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFormSection(formType) {
|
||||
const emailInput = this.renderFormInput({
|
||||
type: 'email',
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
icon: IconEmail,
|
||||
placeholder: 'Email...',
|
||||
required: true,
|
||||
formType: formType
|
||||
});
|
||||
const passwordInput = this.renderFormInput({
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
icon: IconLock,
|
||||
placeholder: 'Password...',
|
||||
required: true,
|
||||
formType: formType
|
||||
});
|
||||
const nameInput = this.renderFormInput({
|
||||
type: 'text',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
icon: IconName,
|
||||
placeholder: 'Name...',
|
||||
required: true,
|
||||
formType: formType
|
||||
});
|
||||
const formText = this.renderFormText({formType});
|
||||
|
||||
let formElements = [];
|
||||
let buttonLabel = '';
|
||||
switch (formType) {
|
||||
case 'signin':
|
||||
buttonLabel = 'Log in';
|
||||
formElements = [emailInput, passwordInput, this.renderFormSubmit({formType, buttonLabel})];
|
||||
break;
|
||||
case 'signup':
|
||||
buttonLabel = 'Sign up';
|
||||
formElements = [nameInput, emailInput, passwordInput, this.renderFormSubmit({formType, buttonLabel})];
|
||||
break;
|
||||
case 'request-password-reset':
|
||||
buttonLabel = 'Send reset password instructions';
|
||||
formElements = [emailInput, this.renderFormSubmit({formType, buttonLabel})];
|
||||
break;
|
||||
case 'password-reset-sent':
|
||||
buttonLabel = 'Resend instructions';
|
||||
formElements = [formText, this.renderFormSubmit({formType, buttonLabel})];
|
||||
break;
|
||||
case 'reset-password':
|
||||
buttonLabel = 'Set password';
|
||||
formElements = [passwordInput, this.renderFormSubmit({formType, buttonLabel})];
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-column nt1">
|
||||
<form className={ `gm-` + formType + `-form` } onSubmit={(e) => this.submitForm(e)} noValidate>
|
||||
{ formElements }
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFormComponent(formType = this.state.formType) {
|
||||
return (
|
||||
<div className="gm-modal-container">
|
||||
<div className="gm-modal gm-auth-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<a className="gm-modal-close" onClick={ (e) => this.close(e)}>{ IconClose }</a>
|
||||
{this.renderFormHeaders(formType)}
|
||||
{this.renderFormSection(formType)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="gm-page-overlay" onClick={(e) => this.close(e)}>
|
||||
{this.renderFormComponent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
close(event) {
|
||||
window.parent.postMessage('pls-close-auth-popup', '*');
|
||||
}
|
||||
}
|
42
ghost/members-api/static/auth/layer0.js
Normal file
42
ghost/members-api/static/auth/layer0.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
/* globals window */
|
||||
module.exports = function layer0(frame) {
|
||||
var getuid = (function (i) {
|
||||
return function () {
|
||||
return i += 1;
|
||||
};
|
||||
})(1);
|
||||
var origin = new URL(frame.getAttribute('src')).origin;
|
||||
var handlers = {};
|
||||
var listener = function () {};
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.origin !== origin) {
|
||||
return;
|
||||
}
|
||||
if (!event.data || !event.data.uid) {
|
||||
if (event.data.event) {
|
||||
return listener(event.data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var handler = handlers[event.data.uid];
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
delete handlers[event.data.uid];
|
||||
handler(event.data.error, event.data.data);
|
||||
});
|
||||
|
||||
function call(method, options, cb) {
|
||||
var uid = getuid();
|
||||
var data = {uid, method, options};
|
||||
handlers[uid] = cb;
|
||||
frame.contentWindow.postMessage(data, origin);
|
||||
}
|
||||
|
||||
function listen(fn) {
|
||||
listener = fn;
|
||||
}
|
||||
|
||||
return {call, listen};
|
||||
};
|
28
ghost/members-api/static/auth/package.json
Normal file
28
ghost/members-api/static/auth/package.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "ghost-member",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "preact build --src=index.js --dest=dist --service-worker=false --no-prerender",
|
||||
"dev": "yarn build --no-production && preact watch --port=8080",
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"eslintIgnore": [
|
||||
"build/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^9.4.2",
|
||||
"cssnano": "^4.1.7",
|
||||
"grunt": "1.0.3",
|
||||
"grunt-shell": "2.1.0",
|
||||
"postcss-color-mod-function": "^3.0.3",
|
||||
"postcss-css-variables": "^0.11.0",
|
||||
"postcss-custom-properties": "^8.0.9",
|
||||
"postcss-import": "^12.0.1",
|
||||
"preact-cli": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"preact": "^8.2.1",
|
||||
"preact-compat": "^3.17.0"
|
||||
}
|
||||
}
|
10
ghost/members-api/static/auth/postcss.config.js
Normal file
10
ghost/members-api/static/auth/postcss.config.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
require('postcss-import'),
|
||||
require('autoprefixer'),
|
||||
require('postcss-css-variables'),
|
||||
require('postcss-color-mod-function'),
|
||||
require('cssnano'),
|
||||
require('postcss-custom-properties')
|
||||
]
|
||||
};
|
26
ghost/members-api/static/auth/preact.config.js
Normal file
26
ghost/members-api/static/auth/preact.config.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
export default function (config, env, helpers) {
|
||||
const postcssLoader = helpers.getLoadersByName(config, 'postcss-loader');
|
||||
const cssLoader = helpers.getLoadersByName(config, 'css-loader');
|
||||
postcssLoader.forEach(({ loader }) => (delete loader.options));
|
||||
cssLoader.forEach(({ loader }) => (delete loader.options));
|
||||
|
||||
helpers.getRulesByMatchingFile(config, '*.css').forEach(({ rule }) => {
|
||||
let filter = (rule.include || rule.exclude || []);
|
||||
let newFilter = filter[0].replace('/components', '/styles');
|
||||
filter.push(newFilter);
|
||||
});
|
||||
|
||||
if (env.production) {
|
||||
config.output.publicPath = 'static/';
|
||||
} else {
|
||||
config.output.publicPath = 'http://localhost:8080/';
|
||||
}
|
||||
config.devServer = {
|
||||
quiet: true,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "*",
|
||||
"Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
|
||||
}
|
||||
}
|
||||
}
|
292
ghost/members-api/static/auth/styles/components.css
Normal file
292
ghost/members-api/static/auth/styles/components.css
Normal file
|
@ -0,0 +1,292 @@
|
|||
/* Reusable components */
|
||||
/* ------------------------------------------------------------
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/* Modal */
|
||||
/* ------------------------------------------------------------ */
|
||||
.gm-page-overlay {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
overflow-y: scroll;
|
||||
background: rgba(10, 17, 23, 0.9);
|
||||
animation: fadeInOverlay 0.2s ease;
|
||||
}
|
||||
|
||||
.gm-modal-container {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.gm-modal {
|
||||
position: relative;
|
||||
background: white;
|
||||
margin: 0 auto;
|
||||
width: 288px;
|
||||
border-radius: 4px;
|
||||
padding: 40px;
|
||||
box-shadow: var(--box-shadow-base);
|
||||
animation: openModal 0.6s ease;
|
||||
}
|
||||
|
||||
.gm-modal-close {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
display: block;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.gm-modal-close svg path {
|
||||
stroke: var(--grey);
|
||||
transition: all var(--animation-speed-base) ease;
|
||||
}
|
||||
|
||||
.gm-modal-close:hover svg path {
|
||||
stroke: var(--grey-d2);
|
||||
}
|
||||
|
||||
@keyframes fadeInOverlay {
|
||||
from {opacity: 0;}
|
||||
to {opacity: 1;}
|
||||
}
|
||||
|
||||
@keyframes openModal { /* Safari and Chrome */
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(25px) scale(0.85);
|
||||
}
|
||||
|
||||
40% {
|
||||
opacity: 1.0;
|
||||
transform: translateY(-8px) scale(1.04);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0) scale(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 440px) {
|
||||
.gm-modal-container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
top: 0;
|
||||
transform: none;
|
||||
height: 100vh;
|
||||
}
|
||||
.gm-modal {
|
||||
width: calc(100% - 48px);
|
||||
height: calc(100vh - 48px);
|
||||
padding: 24px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
/* ------------------------------------------------------------ */
|
||||
button {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--grey);
|
||||
color: var(--grey-d3);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
padding: 0 15px;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
transition: all var(--animation-speed-f1) ease-in-out;
|
||||
position: relative;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
color: var(--blue-l1);
|
||||
}
|
||||
|
||||
.gm-btn-blue {
|
||||
background: var(--blue);
|
||||
background: linear-gradient(to bottom, rgba(62,176,239,1) 0%,rgba(0,139,214,1) 100%);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.gm-btn-blue:active {
|
||||
background: var(--blue-d1);
|
||||
background: linear-gradient(to bottom, rgb(22, 147, 214) 0%,rgb(0, 118, 182) 100%);
|
||||
}
|
||||
|
||||
.gm-btn-blue:hover {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.gm-btn-blue:hover:before {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.gm-btn-blue:before {
|
||||
content: "";
|
||||
transition: all var(--animation-speed-s1) ease;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(0deg,hsla(0,0%,100%,0),hsla(0,0%,100%,.2));
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Forms inputs */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/* Change Autocomplete styles in Chrome*/
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
input:-webkit-autofill:active,
|
||||
textarea:-webkit-autofill,
|
||||
textarea:-webkit-autofill:hover
|
||||
textarea:-webkit-autofill:focus,
|
||||
textarea:-webkit-autofill:active,
|
||||
select:-webkit-autofill,
|
||||
select:-webkit-autofill:hover,
|
||||
select:-webkit-autofill:focus,
|
||||
select:-webkit-autofill:active {
|
||||
-webkit-box-shadow: 0 0 0px 40px #FFF inset;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder,
|
||||
::-moz-placeholder,
|
||||
:-ms-input-placeholder,
|
||||
:-moz-placeholder {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.gm-floating-input {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gm-floating-input input {
|
||||
font-size: var(--text-base);
|
||||
color: var(--grey-d3);
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
border-bottom: 1px solid var(--grey-l1);
|
||||
height: 38px;
|
||||
-webkit-appearance: none;
|
||||
box-sizing: border-box;
|
||||
background: var(--white);
|
||||
height: 44px;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
transition: border var(--animation-speed-f1) ease-in-out;
|
||||
padding: 0 0 1px 26px; /* 1px bottom padding fixes jump that's caused by the border change */
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.gm-floating-input input:hover {
|
||||
border-bottom: 1px solid var(--grey);
|
||||
}
|
||||
|
||||
.gm-floating-input input:focus {
|
||||
border-bottom: 2px solid var(--blue);
|
||||
padding: 0 0 0 26px;
|
||||
}
|
||||
|
||||
.gm-floating-input input.gm-error {
|
||||
border-bottom: 1px solid var(--red);
|
||||
}
|
||||
|
||||
.gm-floating-input label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
font-size: var(--text-xs);
|
||||
padding: 0 0 2px 0;
|
||||
width: 100%;
|
||||
top: 15px;
|
||||
left: 24px;
|
||||
color: var(--grey);
|
||||
transition: all var(--animation-speed-base) ease-in-out;
|
||||
transition-delay: 0.15s;
|
||||
pointer-events: none;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.gm-floating-input input.gm-input-filled + label,
|
||||
.gm-floating-input input:focus + label {
|
||||
opacity: 0;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
.gm-floating-input label i svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.gm-floating-input label i svg path,
|
||||
.gm-floating-input i svg path {
|
||||
stroke: var(--grey);
|
||||
transition: stroke var(--animation-speed-base) ease-in-out;
|
||||
}
|
||||
|
||||
.gm-floating-input input.gm-input-filled + label + i svg path,
|
||||
.gm-floating-input input:focus + label + i svg path{
|
||||
stroke: var(--grey-d2);
|
||||
}
|
||||
|
||||
.gm-floating-input i {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: 0;
|
||||
opacity: 1.0;
|
||||
transition: all var(--animation-speed-f1) ease-in-out;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
.gm-floating-input input.gm-input-filled + label + i,
|
||||
.gm-floating-input input:focus + label + i {
|
||||
opacity: 1.0;
|
||||
transform: translateX(0px);
|
||||
transition-delay: 0.15s;
|
||||
}
|
||||
|
||||
.gm-floating-input label i {
|
||||
font-style: normal;
|
||||
display: inline-block;
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.gm-input-errortext {
|
||||
color: var(--red);
|
||||
font-size: var(--text-s);
|
||||
letter-spacing: 0.4px;
|
||||
margin: 4px 0 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.gm-form-errortext {
|
||||
color: var(--red);
|
||||
font-size: var(--text-s);
|
||||
letter-spacing: 0.4px;
|
||||
margin: 28px -40px 0;
|
||||
background: color-mod(var(--red) a(0.08));
|
||||
padding: 12px 40px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gm-form-errortext i {
|
||||
margin: 3px 8px 0 0;
|
||||
}
|
5
ghost/members-api/static/auth/styles/members.css
Normal file
5
ghost/members-api/static/auth/styles/members.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
@import './normalize.css';
|
||||
@import './utils.css';
|
||||
@import './variables.css';
|
||||
@import './components.css';
|
||||
@import './screen.css';
|
341
ghost/members-api/static/auth/styles/normalize.css
vendored
Normal file
341
ghost/members-api/static/auth/styles/normalize.css
vendored
Normal file
|
@ -0,0 +1,341 @@
|
|||
/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
119
ghost/members-api/static/auth/styles/screen.css
Normal file
119
ghost/members-api/static/auth/styles/screen.css
Normal file
|
@ -0,0 +1,119 @@
|
|||
/* Global styles */
|
||||
/* ------------------------------------------------------------ */
|
||||
html {
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-family: var(--default-font);
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: var(--text-base);
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--grey-d3);
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--grey-d3);
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--blue);
|
||||
transition: color var(--animation-speed-f1) ease-in-out;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--blue-d3);
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
h1 {
|
||||
font-size: var(--text-xl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Auth Modal */
|
||||
/* --------------------------------------------- */
|
||||
.gm-logo {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 4px;
|
||||
background: #343F44 url('../assets/images/ghost-logo.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.gm-auth-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin: 16px 0 0;
|
||||
padding: 12px 0 0;
|
||||
}
|
||||
|
||||
.gm-auth-header -cta {
|
||||
padding: 0 0 3px;
|
||||
}
|
||||
|
||||
.gm-auth-header h1 {
|
||||
font-size: var(--text-xl);
|
||||
}
|
||||
|
||||
.gm-auth-header h4 {
|
||||
font-weight: normal;
|
||||
font-size: var(--text-s);
|
||||
letter-spacing: 0.4px;
|
||||
color: var(--grey-d1);
|
||||
}
|
||||
|
||||
.gm-auth-header a {
|
||||
display: block;
|
||||
font-size: var(--text-s);
|
||||
letter-spacing: 0.4px;
|
||||
padding: 8px;
|
||||
margin: -8px -8px -8px -2px;
|
||||
cursor: pointer;
|
||||
color: var(--blue);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gm-auth-header a:hover {
|
||||
color: var(--blue-d3);
|
||||
}
|
||||
|
||||
.gm-forgot-link {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
font-size: var(--text-s);
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.gm-floating-input .gm-forgot-input {
|
||||
padding-right: 60px;
|
||||
}
|
||||
|
||||
@media (max-width: 440px) {
|
||||
h4 {
|
||||
display: none;
|
||||
}
|
||||
}
|
638
ghost/members-api/static/auth/styles/utils.css
Normal file
638
ghost/members-api/static/auth/styles/utils.css
Normal file
|
@ -0,0 +1,638 @@
|
|||
/* Layout Utitlities */
|
||||
/* ------------------------------------------------------------
|
||||
|
||||
These are generic CSS classes that can be used on containers
|
||||
that have the single purpose of setting up layout.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/* Flexbox */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
.flex { display: flex; }
|
||||
.inline-flex { display: inline-flex; }
|
||||
|
||||
/* 1. Fix for Chrome 44 bug.
|
||||
* https://code.google.com/p/chromium/issues/detail?id=506893 */
|
||||
.flex-auto {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0; /* 1 */
|
||||
min-height: 0; /* 1 */
|
||||
}
|
||||
|
||||
.flex-none { flex: none; }
|
||||
|
||||
.flex-column { flex-direction: column; }
|
||||
.flex-row { flex-direction: row; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.flex-nowrap { flex-wrap: nowrap; }
|
||||
.flex-wrap-reverse { flex-wrap: wrap-reverse; }
|
||||
.flex-column-reverse { flex-direction: column-reverse; }
|
||||
.flex-row-reverse { flex-direction: row-reverse; }
|
||||
|
||||
.items-start { align-items: flex-start; }
|
||||
.items-end { align-items: flex-end; }
|
||||
.items-center { align-items: center; }
|
||||
.items-baseline { align-items: baseline; }
|
||||
.items-stretch { align-items: stretch; }
|
||||
|
||||
.self-start { align-self: flex-start; }
|
||||
.self-end { align-self: flex-end; }
|
||||
.self-center { align-self: center; }
|
||||
.self-baseline { align-self: baseline; }
|
||||
.self-stretch { align-self: stretch; }
|
||||
|
||||
.justify-start { justify-content: flex-start; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.justify-around { justify-content: space-around; }
|
||||
|
||||
.content-start { align-content: flex-start; }
|
||||
.content-end { align-content: flex-end; }
|
||||
.content-center { align-content: center; }
|
||||
.content-between { align-content: space-between; }
|
||||
.content-around { align-content: space-around; }
|
||||
.content-stretch { align-content: stretch; }
|
||||
|
||||
.order-0 { order: 0; }
|
||||
.order-1 { order: 1; }
|
||||
.order-2 { order: 2; }
|
||||
.order-3 { order: 3; }
|
||||
.order-4 { order: 4; }
|
||||
.order-5 { order: 5; }
|
||||
.order-6 { order: 6; }
|
||||
.order-7 { order: 7; }
|
||||
.order-8 { order: 8; }
|
||||
.order-last { order: 99999; }
|
||||
|
||||
.flex-grow-0 { flex-grow: 0; }
|
||||
.flex-grow-1 { flex-grow: 1; }
|
||||
|
||||
.flex-shrink-0 { flex-shrink: 0; }
|
||||
.flex-shrink-1 { flex-shrink: 1; }
|
||||
|
||||
|
||||
/* Margins and paddings */
|
||||
/* ------------------------------------------------------------ */
|
||||
:root {
|
||||
--grid-size: 0.4rem;
|
||||
}
|
||||
|
||||
.pa0 { padding: 0; }
|
||||
.pa1 { padding: calc(var(--grid-size) * 1); }
|
||||
.pa2 { padding: calc(var(--grid-size) * 2); }
|
||||
.pa3 { padding: calc(var(--grid-size) * 3); }
|
||||
.pa4 { padding: calc(var(--grid-size) * 4); }
|
||||
.pa5 { padding: calc(var(--grid-size) * 5); }
|
||||
.pa6 { padding: calc(var(--grid-size) * 6); }
|
||||
.pa7 { padding: calc(var(--grid-size) * 7); }
|
||||
.pa8 { padding: calc(var(--grid-size) * 8); }
|
||||
.pa9 { padding: calc(var(--grid-size) * 9); }
|
||||
.pa10 { padding: calc(var(--grid-size) * 10); }
|
||||
.pa11 { padding: calc(var(--grid-size) * 11); }
|
||||
.pa12 { padding: calc(var(--grid-size) * 12); }
|
||||
.pa13 { padding: calc(var(--grid-size) * 13); }
|
||||
.pa14 { padding: calc(var(--grid-size) * 14); }
|
||||
.pa15 { padding: calc(var(--grid-size) * 15); }
|
||||
.pa16 { padding: calc(var(--grid-size) * 16); }
|
||||
.pa17 { padding: calc(var(--grid-size) * 17); }
|
||||
.pa18 { padding: calc(var(--grid-size) * 18); }
|
||||
.pa19 { padding: calc(var(--grid-size) * 19); }
|
||||
.pa20 { padding: calc(var(--grid-size) * 20); }
|
||||
.pa25 { padding: calc(var(--grid-size) * 25); }
|
||||
.pa30 { padding: calc(var(--grid-size) * 30); }
|
||||
.pa40 { padding: calc(var(--grid-size) * 40); }
|
||||
.pa50 { padding: calc(var(--grid-size) * 50); }
|
||||
|
||||
.pr0 { padding-right: 0; }
|
||||
.pr1 { padding-right: calc(var(--grid-size) * 1); }
|
||||
.pr2 { padding-right: calc(var(--grid-size) * 2); }
|
||||
.pr3 { padding-right: calc(var(--grid-size) * 3); }
|
||||
.pr4 { padding-right: calc(var(--grid-size) * 4); }
|
||||
.pr5 { padding-right: calc(var(--grid-size) * 5); }
|
||||
.pr6 { padding-right: calc(var(--grid-size) * 6); }
|
||||
.pr7 { padding-right: calc(var(--grid-size) * 7); }
|
||||
.pr8 { padding-right: calc(var(--grid-size) * 8); }
|
||||
.pr9 { padding-right: calc(var(--grid-size) * 9); }
|
||||
.pr10 { padding-right: calc(var(--grid-size) * 10); }
|
||||
.pr11 { padding-right: calc(var(--grid-size) * 11); }
|
||||
.pr12 { padding-right: calc(var(--grid-size) * 12); }
|
||||
.pr13 { padding-right: calc(var(--grid-size) * 13); }
|
||||
.pr14 { padding-right: calc(var(--grid-size) * 14); }
|
||||
.pr15 { padding-right: calc(var(--grid-size) * 15); }
|
||||
.pr16 { padding-right: calc(var(--grid-size) * 16); }
|
||||
.pr17 { padding-right: calc(var(--grid-size) * 17); }
|
||||
.pr18 { padding-right: calc(var(--grid-size) * 18); }
|
||||
.pr19 { padding-right: calc(var(--grid-size) * 19); }
|
||||
.pr20 { padding-right: calc(var(--grid-size) * 20); }
|
||||
.pr25 { padding-right: calc(var(--grid-size) * 25); }
|
||||
.pr30 { padding-right: calc(var(--grid-size) * 30); }
|
||||
.pr40 { padding-right: calc(var(--grid-size) * 40); }
|
||||
.pr50 { padding-right: calc(var(--grid-size) * 50); }
|
||||
|
||||
.pb0 { padding-bottom: 0; }
|
||||
.pb1 { padding-bottom: calc(var(--grid-size) * 1); }
|
||||
.pb2 { padding-bottom: calc(var(--grid-size) * 2); }
|
||||
.pb3 { padding-bottom: calc(var(--grid-size) * 3); }
|
||||
.pb4 { padding-bottom: calc(var(--grid-size) * 4); }
|
||||
.pb5 { padding-bottom: calc(var(--grid-size) * 5); }
|
||||
.pb6 { padding-bottom: calc(var(--grid-size) * 6); }
|
||||
.pb7 { padding-bottom: calc(var(--grid-size) * 7); }
|
||||
.pb8 { padding-bottom: calc(var(--grid-size) * 8); }
|
||||
.pb9 { padding-bottom: calc(var(--grid-size) * 9); }
|
||||
.pb10 { padding-bottom: calc(var(--grid-size) * 10); }
|
||||
.pb11 { padding-bottom: calc(var(--grid-size) * 11); }
|
||||
.pb12 { padding-bottom: calc(var(--grid-size) * 12); }
|
||||
.pb13 { padding-bottom: calc(var(--grid-size) * 13); }
|
||||
.pb14 { padding-bottom: calc(var(--grid-size) * 14); }
|
||||
.pb15 { padding-bottom: calc(var(--grid-size) * 15); }
|
||||
.pb16 { padding-bottom: calc(var(--grid-size) * 16); }
|
||||
.pb17 { padding-bottom: calc(var(--grid-size) * 17); }
|
||||
.pb18 { padding-bottom: calc(var(--grid-size) * 18); }
|
||||
.pb19 { padding-bottom: calc(var(--grid-size) * 19); }
|
||||
.pb20 { padding-bottom: calc(var(--grid-size) * 20); }
|
||||
.pb25 { padding-bottom: calc(var(--grid-size) * 25); }
|
||||
.pb30 { padding-bottom: calc(var(--grid-size) * 30); }
|
||||
.pb40 { padding-bottom: calc(var(--grid-size) * 40); }
|
||||
.pb50 { padding-bottom: calc(var(--grid-size) * 50); }
|
||||
|
||||
.pl0 { padding-left: 0; }
|
||||
.pl1 { padding-left: calc(var(--grid-size) * 1); }
|
||||
.pl2 { padding-left: calc(var(--grid-size) * 2); }
|
||||
.pl3 { padding-left: calc(var(--grid-size) * 3); }
|
||||
.pl4 { padding-left: calc(var(--grid-size) * 4); }
|
||||
.pl5 { padding-left: calc(var(--grid-size) * 5); }
|
||||
.pl6 { padding-left: calc(var(--grid-size) * 6); }
|
||||
.pl7 { padding-left: calc(var(--grid-size) * 7); }
|
||||
.pl8 { padding-left: calc(var(--grid-size) * 8); }
|
||||
.pl9 { padding-left: calc(var(--grid-size) * 9); }
|
||||
.pl10 { padding-left: calc(var(--grid-size) * 10); }
|
||||
.pl11 { padding-left: calc(var(--grid-size) * 11); }
|
||||
.pl12 { padding-left: calc(var(--grid-size) * 12); }
|
||||
.pl13 { padding-left: calc(var(--grid-size) * 13); }
|
||||
.pl14 { padding-left: calc(var(--grid-size) * 14); }
|
||||
.pl15 { padding-left: calc(var(--grid-size) * 15); }
|
||||
.pl16 { padding-left: calc(var(--grid-size) * 16); }
|
||||
.pl17 { padding-left: calc(var(--grid-size) * 17); }
|
||||
.pl18 { padding-left: calc(var(--grid-size) * 18); }
|
||||
.pl19 { padding-left: calc(var(--grid-size) * 19); }
|
||||
.pl20 { padding-left: calc(var(--grid-size) * 20); }
|
||||
.pl25 { padding-left: calc(var(--grid-size) * 25); }
|
||||
.pl30 { padding-left: calc(var(--grid-size) * 30); }
|
||||
.pl40 { padding-left: calc(var(--grid-size) * 40); }
|
||||
.pl50 { padding-left: calc(var(--grid-size) * 50); }
|
||||
|
||||
.pt0 { padding-top: 0; }
|
||||
.pt1 { padding-top: calc(var(--grid-size) * 1); }
|
||||
.pt2 { padding-top: calc(var(--grid-size) * 2); }
|
||||
.pt3 { padding-top: calc(var(--grid-size) * 3); }
|
||||
.pt4 { padding-top: calc(var(--grid-size) * 4); }
|
||||
.pt5 { padding-top: calc(var(--grid-size) * 5); }
|
||||
.pt6 { padding-top: calc(var(--grid-size) * 6); }
|
||||
.pt7 { padding-top: calc(var(--grid-size) * 7); }
|
||||
.pt8 { padding-top: calc(var(--grid-size) * 8); }
|
||||
.pt9 { padding-top: calc(var(--grid-size) * 9); }
|
||||
.pt10 { padding-top: calc(var(--grid-size) * 10); }
|
||||
.pt11 { padding-top: calc(var(--grid-size) * 11); }
|
||||
.pt12 { padding-top: calc(var(--grid-size) * 12); }
|
||||
.pt13 { padding-top: calc(var(--grid-size) * 13); }
|
||||
.pt14 { padding-top: calc(var(--grid-size) * 14); }
|
||||
.pt15 { padding-top: calc(var(--grid-size) * 15); }
|
||||
.pt16 { padding-top: calc(var(--grid-size) * 16); }
|
||||
.pt17 { padding-top: calc(var(--grid-size) * 17); }
|
||||
.pt18 { padding-top: calc(var(--grid-size) * 18); }
|
||||
.pt19 { padding-top: calc(var(--grid-size) * 19); }
|
||||
.pt20 { padding-top: calc(var(--grid-size) * 20); }
|
||||
.pt25 { padding-top: calc(var(--grid-size) * 25); }
|
||||
.pt30 { padding-top: calc(var(--grid-size) * 30); }
|
||||
.pt40 { padding-top: calc(var(--grid-size) * 40); }
|
||||
.pt50 { padding-top: calc(var(--grid-size) * 50); }
|
||||
|
||||
.ma0 { margin: 0; }
|
||||
.ma1 { margin: calc(var(--grid-size) * 1); }
|
||||
.ma2 { margin: calc(var(--grid-size) * 2); }
|
||||
.ma3 { margin: calc(var(--grid-size) * 3); }
|
||||
.ma4 { margin: calc(var(--grid-size) * 4); }
|
||||
.ma5 { margin: calc(var(--grid-size) * 5); }
|
||||
.ma6 { margin: calc(var(--grid-size) * 6); }
|
||||
.ma7 { margin: calc(var(--grid-size) * 7); }
|
||||
.ma8 { margin: calc(var(--grid-size) * 8); }
|
||||
.ma9 { margin: calc(var(--grid-size) * 9); }
|
||||
.ma10 { margin: calc(var(--grid-size) * 10); }
|
||||
.ma11 { margin: calc(var(--grid-size) * 11); }
|
||||
.ma12 { margin: calc(var(--grid-size) * 12); }
|
||||
.ma13 { margin: calc(var(--grid-size) * 13); }
|
||||
.ma14 { margin: calc(var(--grid-size) * 14); }
|
||||
.ma15 { margin: calc(var(--grid-size) * 15); }
|
||||
.ma16 { margin: calc(var(--grid-size) * 16); }
|
||||
.ma17 { margin: calc(var(--grid-size) * 17); }
|
||||
.ma18 { margin: calc(var(--grid-size) * 18); }
|
||||
.ma19 { margin: calc(var(--grid-size) * 19); }
|
||||
.ma20 { margin: calc(var(--grid-size) * 20); }
|
||||
.ma25 { margin: calc(var(--grid-size) * 25); }
|
||||
.ma30 { margin: calc(var(--grid-size) * 30); }
|
||||
.ma40 { margin: calc(var(--grid-size) * 40); }
|
||||
.ma50 { margin: calc(var(--grid-size) * 50); }
|
||||
|
||||
.mr0 { margin-right: 0; }
|
||||
.mr1 { margin-right: calc(var(--grid-size) * 1); }
|
||||
.mr2 { margin-right: calc(var(--grid-size) * 2); }
|
||||
.mr3 { margin-right: calc(var(--grid-size) * 3); }
|
||||
.mr4 { margin-right: calc(var(--grid-size) * 4); }
|
||||
.mr5 { margin-right: calc(var(--grid-size) * 5); }
|
||||
.mr6 { margin-right: calc(var(--grid-size) * 6); }
|
||||
.mr7 { margin-right: calc(var(--grid-size) * 7); }
|
||||
.mr8 { margin-right: calc(var(--grid-size) * 8); }
|
||||
.mr9 { margin-right: calc(var(--grid-size) * 9); }
|
||||
.mr10 { margin-right: calc(var(--grid-size) * 10); }
|
||||
.mr11 { margin-right: calc(var(--grid-size) * 11); }
|
||||
.mr12 { margin-right: calc(var(--grid-size) * 12); }
|
||||
.mr13 { margin-right: calc(var(--grid-size) * 13); }
|
||||
.mr14 { margin-right: calc(var(--grid-size) * 14); }
|
||||
.mr15 { margin-right: calc(var(--grid-size) * 15); }
|
||||
.mr16 { margin-right: calc(var(--grid-size) * 16); }
|
||||
.mr17 { margin-right: calc(var(--grid-size) * 17); }
|
||||
.mr18 { margin-right: calc(var(--grid-size) * 18); }
|
||||
.mr19 { margin-right: calc(var(--grid-size) * 19); }
|
||||
.mr20 { margin-right: calc(var(--grid-size) * 20); }
|
||||
.mr25 { margin-right: calc(var(--grid-size) * 25); }
|
||||
.mr30 { margin-right: calc(var(--grid-size) * 30); }
|
||||
.mr40 { margin-right: calc(var(--grid-size) * 40); }
|
||||
.mr50 { margin-right: calc(var(--grid-size) * 50); }
|
||||
|
||||
.mb0 { margin-bottom: 0; }
|
||||
.mb1 { margin-bottom: calc(var(--grid-size) * 1); }
|
||||
.mb2 { margin-bottom: calc(var(--grid-size) * 2); }
|
||||
.mb3 { margin-bottom: calc(var(--grid-size) * 3); }
|
||||
.mb4 { margin-bottom: calc(var(--grid-size) * 4); }
|
||||
.mb5 { margin-bottom: calc(var(--grid-size) * 5); }
|
||||
.mb6 { margin-bottom: calc(var(--grid-size) * 6); }
|
||||
.mb7 { margin-bottom: calc(var(--grid-size) * 7); }
|
||||
.mb8 { margin-bottom: calc(var(--grid-size) * 8); }
|
||||
.mb9 { margin-bottom: calc(var(--grid-size) * 9); }
|
||||
.mb10 { margin-bottom: calc(var(--grid-size) * 10); }
|
||||
.mb11 { margin-bottom: calc(var(--grid-size) * 11); }
|
||||
.mb12 { margin-bottom: calc(var(--grid-size) * 12); }
|
||||
.mb13 { margin-bottom: calc(var(--grid-size) * 13); }
|
||||
.mb14 { margin-bottom: calc(var(--grid-size) * 14); }
|
||||
.mb15 { margin-bottom: calc(var(--grid-size) * 15); }
|
||||
.mb16 { margin-bottom: calc(var(--grid-size) * 16); }
|
||||
.mb17 { margin-bottom: calc(var(--grid-size) * 17); }
|
||||
.mb18 { margin-bottom: calc(var(--grid-size) * 18); }
|
||||
.mb19 { margin-bottom: calc(var(--grid-size) * 19); }
|
||||
.mb20 { margin-bottom: calc(var(--grid-size) * 20); }
|
||||
.mb25 { margin-bottom: calc(var(--grid-size) * 25); }
|
||||
.mb30 { margin-bottom: calc(var(--grid-size) * 30); }
|
||||
.mb40 { margin-bottom: calc(var(--grid-size) * 40); }
|
||||
.mb50 { margin-bottom: calc(var(--grid-size) * 50); }
|
||||
|
||||
.ml0 { margin-left: 0; }
|
||||
.ml1 { margin-left: calc(var(--grid-size) * 1); }
|
||||
.ml2 { margin-left: calc(var(--grid-size) * 2); }
|
||||
.ml3 { margin-left: calc(var(--grid-size) * 3); }
|
||||
.ml4 { margin-left: calc(var(--grid-size) * 4); }
|
||||
.ml5 { margin-left: calc(var(--grid-size) * 5); }
|
||||
.ml6 { margin-left: calc(var(--grid-size) * 6); }
|
||||
.ml7 { margin-left: calc(var(--grid-size) * 7); }
|
||||
.ml8 { margin-left: calc(var(--grid-size) * 8); }
|
||||
.ml9 { margin-left: calc(var(--grid-size) * 9); }
|
||||
.ml10 { margin-left: calc(var(--grid-size) * 10); }
|
||||
.ml11 { margin-left: calc(var(--grid-size) * 11); }
|
||||
.ml12 { margin-left: calc(var(--grid-size) * 12); }
|
||||
.ml13 { margin-left: calc(var(--grid-size) * 13); }
|
||||
.ml14 { margin-left: calc(var(--grid-size) * 14); }
|
||||
.ml15 { margin-left: calc(var(--grid-size) * 15); }
|
||||
.ml16 { margin-left: calc(var(--grid-size) * 16); }
|
||||
.ml17 { margin-left: calc(var(--grid-size) * 17); }
|
||||
.ml18 { margin-left: calc(var(--grid-size) * 18); }
|
||||
.ml19 { margin-left: calc(var(--grid-size) * 19); }
|
||||
.ml20 { margin-left: calc(var(--grid-size) * 20); }
|
||||
.ml25 { margin-left: calc(var(--grid-size) * 25); }
|
||||
.ml30 { margin-left: calc(var(--grid-size) * 30); }
|
||||
.ml40 { margin-left: calc(var(--grid-size) * 40); }
|
||||
.ml50 { margin-left: calc(var(--grid-size) * 50); }
|
||||
|
||||
.mt0 { margin-top: 0; }
|
||||
.mt1 { margin-top: calc(var(--grid-size) * 1); }
|
||||
.mt2 { margin-top: calc(var(--grid-size) * 2); }
|
||||
.mt3 { margin-top: calc(var(--grid-size) * 3); }
|
||||
.mt4 { margin-top: calc(var(--grid-size) * 4); }
|
||||
.mt5 { margin-top: calc(var(--grid-size) * 5); }
|
||||
.mt6 { margin-top: calc(var(--grid-size) * 6); }
|
||||
.mt7 { margin-top: calc(var(--grid-size) * 7); }
|
||||
.mt8 { margin-top: calc(var(--grid-size) * 8); }
|
||||
.mt9 { margin-top: calc(var(--grid-size) * 9); }
|
||||
.mt10 { margin-top: calc(var(--grid-size) * 10); }
|
||||
.mt11 { margin-top: calc(var(--grid-size) * 11); }
|
||||
.mt12 { margin-top: calc(var(--grid-size) * 12); }
|
||||
.mt13 { margin-top: calc(var(--grid-size) * 13); }
|
||||
.mt14 { margin-top: calc(var(--grid-size) * 14); }
|
||||
.mt15 { margin-top: calc(var(--grid-size) * 15); }
|
||||
.mt16 { margin-top: calc(var(--grid-size) * 16); }
|
||||
.mt17 { margin-top: calc(var(--grid-size) * 17); }
|
||||
.mt18 { margin-top: calc(var(--grid-size) * 18); }
|
||||
.mt19 { margin-top: calc(var(--grid-size) * 19); }
|
||||
.mt20 { margin-top: calc(var(--grid-size) * 20); }
|
||||
.mt25 { margin-top: calc(var(--grid-size) * 25); }
|
||||
.mt30 { margin-top: calc(var(--grid-size) * 30); }
|
||||
.mt40 { margin-top: calc(var(--grid-size) * 40); }
|
||||
.mt50 { margin-top: calc(var(--grid-size) * 50); }
|
||||
|
||||
.na0 { margin: 0; }
|
||||
.na1 { margin: calc(-1 * var(--grid-size) * 1); }
|
||||
.na2 { margin: calc(-1 * var(--grid-size) * 2); }
|
||||
.na3 { margin: calc(-1 * var(--grid-size) * 3); }
|
||||
.na4 { margin: calc(-1 * var(--grid-size) * 4); }
|
||||
.na5 { margin: calc(-1 * var(--grid-size) * 5); }
|
||||
.na6 { margin: calc(-1 * var(--grid-size) * 6); }
|
||||
.na7 { margin: calc(-1 * var(--grid-size) * 7); }
|
||||
.na8 { margin: calc(-1 * var(--grid-size) * 8); }
|
||||
.na9 { margin: calc(-1 * var(--grid-size) * 9); }
|
||||
.na10 { margin: calc(-1 * var(--grid-size) * 10); }
|
||||
.na11 { margin: calc(-1 * var(--grid-size) * 11); }
|
||||
.na12 { margin: calc(-1 * var(--grid-size) * 12); }
|
||||
.na13 { margin: calc(-1 * var(--grid-size) * 13); }
|
||||
.na14 { margin: calc(-1 * var(--grid-size) * 14); }
|
||||
.na15 { margin: calc(-1 * var(--grid-size) * 15); }
|
||||
.na16 { margin: calc(-1 * var(--grid-size) * 16); }
|
||||
.na17 { margin: calc(-1 * var(--grid-size) * 17); }
|
||||
.na18 { margin: calc(-1 * var(--grid-size) * 18); }
|
||||
.na19 { margin: calc(-1 * var(--grid-size) * 19); }
|
||||
.na20 { margin: calc(-1 * var(--grid-size) * 20); }
|
||||
.na25 { margin: calc(-1 * var(--grid-size) * 25); }
|
||||
.na30 { margin: calc(-1 * var(--grid-size) * 30); }
|
||||
.na40 { margin: calc(-1 * var(--grid-size) * 40); }
|
||||
.na50 { margin: calc(-1 * var(--grid-size) * 50); }
|
||||
|
||||
.nr0 { margin-right: 0; }
|
||||
.nr1 { margin-right: calc(-1 * var(--grid-size) * 1); }
|
||||
.nr2 { margin-right: calc(-1 * var(--grid-size) * 2); }
|
||||
.nr3 { margin-right: calc(-1 * var(--grid-size) * 3); }
|
||||
.nr4 { margin-right: calc(-1 * var(--grid-size) * 4); }
|
||||
.nr5 { margin-right: calc(-1 * var(--grid-size) * 5); }
|
||||
.nr6 { margin-right: calc(-1 * var(--grid-size) * 6); }
|
||||
.nr7 { margin-right: calc(-1 * var(--grid-size) * 7); }
|
||||
.nr8 { margin-right: calc(-1 * var(--grid-size) * 8); }
|
||||
.nr9 { margin-right: calc(-1 * var(--grid-size) * 9); }
|
||||
.nr10 { margin-right: calc(-1 * var(--grid-size) * 10); }
|
||||
.nr11 { margin-right: calc(-1 * var(--grid-size) * 11); }
|
||||
.nr12 { margin-right: calc(-1 * var(--grid-size) * 12); }
|
||||
.nr13 { margin-right: calc(-1 * var(--grid-size) * 13); }
|
||||
.nr14 { margin-right: calc(-1 * var(--grid-size) * 14); }
|
||||
.nr15 { margin-right: calc(-1 * var(--grid-size) * 15); }
|
||||
.nr16 { margin-right: calc(-1 * var(--grid-size) * 16); }
|
||||
.nr17 { margin-right: calc(-1 * var(--grid-size) * 17); }
|
||||
.nr18 { margin-right: calc(-1 * var(--grid-size) * 18); }
|
||||
.nr19 { margin-right: calc(-1 * var(--grid-size) * 19); }
|
||||
.nr20 { margin-right: calc(-1 * var(--grid-size) * 20); }
|
||||
.nr25 { margin-right: calc(-1 * var(--grid-size) * 25); }
|
||||
.nr30 { margin-right: calc(-1 * var(--grid-size) * 30); }
|
||||
.nr40 { margin-right: calc(-1 * var(--grid-size) * 40); }
|
||||
.nr50 { margin-right: calc(-1 * var(--grid-size) * 50); }
|
||||
|
||||
.nb0 { margin-bottom: 0; }
|
||||
.nb1 { margin-bottom: calc(-1 * var(--grid-size) * 1); }
|
||||
.nb2 { margin-bottom: calc(-1 * var(--grid-size) * 2); }
|
||||
.nb3 { margin-bottom: calc(-1 * var(--grid-size) * 3); }
|
||||
.nb4 { margin-bottom: calc(-1 * var(--grid-size) * 4); }
|
||||
.nb5 { margin-bottom: calc(-1 * var(--grid-size) * 5); }
|
||||
.nb6 { margin-bottom: calc(-1 * var(--grid-size) * 6); }
|
||||
.nb7 { margin-bottom: calc(-1 * var(--grid-size) * 7); }
|
||||
.nb8 { margin-bottom: calc(-1 * var(--grid-size) * 8); }
|
||||
.nb9 { margin-bottom: calc(-1 * var(--grid-size) * 9); }
|
||||
.nb10 { margin-bottom: calc(-1 * var(--grid-size) * 10); }
|
||||
.nb11 { margin-bottom: calc(-1 * var(--grid-size) * 11); }
|
||||
.nb12 { margin-bottom: calc(-1 * var(--grid-size) * 12); }
|
||||
.nb13 { margin-bottom: calc(-1 * var(--grid-size) * 13); }
|
||||
.nb14 { margin-bottom: calc(-1 * var(--grid-size) * 14); }
|
||||
.nb15 { margin-bottom: calc(-1 * var(--grid-size) * 15); }
|
||||
.nb16 { margin-bottom: calc(-1 * var(--grid-size) * 16); }
|
||||
.nb17 { margin-bottom: calc(-1 * var(--grid-size) * 17); }
|
||||
.nb18 { margin-bottom: calc(-1 * var(--grid-size) * 18); }
|
||||
.nb19 { margin-bottom: calc(-1 * var(--grid-size) * 19); }
|
||||
.nb20 { margin-bottom: calc(-1 * var(--grid-size) * 20); }
|
||||
.nb25 { margin-bottom: calc(-1 * var(--grid-size) * 25); }
|
||||
.nb30 { margin-bottom: calc(-1 * var(--grid-size) * 30); }
|
||||
.nb40 { margin-bottom: calc(-1 * var(--grid-size) * 40); }
|
||||
.nb50 { margin-bottom: calc(-1 * var(--grid-size) * 50); }
|
||||
|
||||
.nl0 { margin-left: 0; }
|
||||
.nl1 { margin-left: calc(-1 * var(--grid-size) * 1); }
|
||||
.nl2 { margin-left: calc(-1 * var(--grid-size) * 2); }
|
||||
.nl3 { margin-left: calc(-1 * var(--grid-size) * 3); }
|
||||
.nl4 { margin-left: calc(-1 * var(--grid-size) * 4); }
|
||||
.nl5 { margin-left: calc(-1 * var(--grid-size) * 5); }
|
||||
.nl6 { margin-left: calc(-1 * var(--grid-size) * 6); }
|
||||
.nl7 { margin-left: calc(-1 * var(--grid-size) * 7); }
|
||||
.nl8 { margin-left: calc(-1 * var(--grid-size) * 8); }
|
||||
.nl9 { margin-left: calc(-1 * var(--grid-size) * 9); }
|
||||
.nl10 { margin-left: calc(-1 * var(--grid-size) * 10); }
|
||||
.nl11 { margin-left: calc(-1 * var(--grid-size) * 11); }
|
||||
.nl12 { margin-left: calc(-1 * var(--grid-size) * 12); }
|
||||
.nl13 { margin-left: calc(-1 * var(--grid-size) * 13); }
|
||||
.nl14 { margin-left: calc(-1 * var(--grid-size) * 14); }
|
||||
.nl15 { margin-left: calc(-1 * var(--grid-size) * 15); }
|
||||
.nl16 { margin-left: calc(-1 * var(--grid-size) * 16); }
|
||||
.nl17 { margin-left: calc(-1 * var(--grid-size) * 17); }
|
||||
.nl18 { margin-left: calc(-1 * var(--grid-size) * 18); }
|
||||
.nl19 { margin-left: calc(-1 * var(--grid-size) * 19); }
|
||||
.nl20 { margin-left: calc(-1 * var(--grid-size) * 20); }
|
||||
.nl25 { margin-left: calc(-1 * var(--grid-size) * 25); }
|
||||
.nl30 { margin-left: calc(-1 * var(--grid-size) * 30); }
|
||||
.nl40 { margin-left: calc(-1 * var(--grid-size) * 40); }
|
||||
.nl50 { margin-left: calc(-1 * var(--grid-size) * 50); }
|
||||
|
||||
.nt0 { margin-top: 0; }
|
||||
.nt1 { margin-top: calc(-1 * var(--grid-size) * 1); }
|
||||
.nt2 { margin-top: calc(-1 * var(--grid-size) * 2); }
|
||||
.nt3 { margin-top: calc(-1 * var(--grid-size) * 3); }
|
||||
.nt4 { margin-top: calc(-1 * var(--grid-size) * 4); }
|
||||
.nt5 { margin-top: calc(-1 * var(--grid-size) * 5); }
|
||||
.nt6 { margin-top: calc(-1 * var(--grid-size) * 6); }
|
||||
.nt7 { margin-top: calc(-1 * var(--grid-size) * 7); }
|
||||
.nt8 { margin-top: calc(-1 * var(--grid-size) * 8); }
|
||||
.nt9 { margin-top: calc(-1 * var(--grid-size) * 9); }
|
||||
.nt10 { margin-top: calc(-1 * var(--grid-size) * 10); }
|
||||
.nt11 { margin-top: calc(-1 * var(--grid-size) * 11); }
|
||||
.nt12 { margin-top: calc(-1 * var(--grid-size) * 12); }
|
||||
.nt13 { margin-top: calc(-1 * var(--grid-size) * 13); }
|
||||
.nt14 { margin-top: calc(-1 * var(--grid-size) * 14); }
|
||||
.nt15 { margin-top: calc(-1 * var(--grid-size) * 15); }
|
||||
.nt16 { margin-top: calc(-1 * var(--grid-size) * 16); }
|
||||
.nt17 { margin-top: calc(-1 * var(--grid-size) * 17); }
|
||||
.nt18 { margin-top: calc(-1 * var(--grid-size) * 18); }
|
||||
.nt19 { margin-top: calc(-1 * var(--grid-size) * 19); }
|
||||
.nt20 { margin-top: calc(-1 * var(--grid-size) * 20); }
|
||||
.nt25 { margin-top: calc(-1 * var(--grid-size) * 25); }
|
||||
.nt30 { margin-top: calc(-1 * var(--grid-size) * 30); }
|
||||
.nt40 { margin-top: calc(-1 * var(--grid-size) * 40); }
|
||||
.nt50 { margin-top: calc(-1 * var(--grid-size) * 50); }
|
||||
|
||||
|
||||
/* Nudging */
|
||||
/* ------------------------------------------------------------ */
|
||||
.nudge-top--1 {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.nudge-top--2 {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.nudge-top--3 {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
.nudge-top--4 {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
.nudge-top--5 {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
}
|
||||
.nudge-top--6 {
|
||||
position: relative;
|
||||
top: 6px;
|
||||
}
|
||||
.nudge-top--7 {
|
||||
position: relative;
|
||||
top: 7px;
|
||||
}
|
||||
.nudge-top--8 {
|
||||
position: relative;
|
||||
top: 8px;
|
||||
}
|
||||
.nudge-top--9 {
|
||||
position: relative;
|
||||
top: 9px;
|
||||
}
|
||||
.nudge-top--10 {
|
||||
position: relative;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.nudge-right--1 {
|
||||
position: relative;
|
||||
right: 1px;
|
||||
}
|
||||
.nudge-right--2 {
|
||||
position: relative;
|
||||
right: 2px;
|
||||
}
|
||||
.nudge-right--3 {
|
||||
position: relative;
|
||||
right: 3px;
|
||||
}
|
||||
.nudge-right--4 {
|
||||
position: relative;
|
||||
right: 4px;
|
||||
}
|
||||
.nudge-right--5 {
|
||||
position: relative;
|
||||
right: 5px;
|
||||
}
|
||||
.nudge-right--6 {
|
||||
position: relative;
|
||||
right: 6px;
|
||||
}
|
||||
.nudge-right--7 {
|
||||
position: relative;
|
||||
right: 7px;
|
||||
}
|
||||
.nudge-right--8 {
|
||||
position: relative;
|
||||
right: 8px;
|
||||
}
|
||||
.nudge-right--9 {
|
||||
position: relative;
|
||||
right: 9px;
|
||||
}
|
||||
.nudge-right--10 {
|
||||
position: relative;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.nudge-bottom--1 {
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
}
|
||||
.nudge-bottom--2 {
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
}
|
||||
.nudge-bottom--3 {
|
||||
position: relative;
|
||||
bottom: 3px;
|
||||
}
|
||||
.nudge-bottom--4 {
|
||||
position: relative;
|
||||
bottom: 4px;
|
||||
}
|
||||
.nudge-bottom--5 {
|
||||
position: relative;
|
||||
bottom: 5px;
|
||||
}
|
||||
.nudge-bottom--6 {
|
||||
position: relative;
|
||||
bottom: 6px;
|
||||
}
|
||||
.nudge-bottom--7 {
|
||||
position: relative;
|
||||
bottom: 7px;
|
||||
}
|
||||
.nudge-bottom--8 {
|
||||
position: relative;
|
||||
bottom: 8px;
|
||||
}
|
||||
.nudge-bottom--9 {
|
||||
position: relative;
|
||||
bottom: 9px;
|
||||
}
|
||||
.nudge-bottom--10 {
|
||||
position: relative;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.nudge-left--1 {
|
||||
position: relative;
|
||||
left: 1px;
|
||||
}
|
||||
.nudge-left--2 {
|
||||
position: relative;
|
||||
left: 2px;
|
||||
}
|
||||
.nudge-left--3 {
|
||||
position: relative;
|
||||
left: 3px;
|
||||
}
|
||||
.nudge-left--4 {
|
||||
position: relative;
|
||||
left: 4px;
|
||||
}
|
||||
.nudge-left--5 {
|
||||
position: relative;
|
||||
left: 5px;
|
||||
}
|
||||
.nudge-left--6 {
|
||||
position: relative;
|
||||
left: 6px;
|
||||
}
|
||||
.nudge-left--7 {
|
||||
position: relative;
|
||||
left: 7px;
|
||||
}
|
||||
.nudge-left--8 {
|
||||
position: relative;
|
||||
left: 8px;
|
||||
}
|
||||
.nudge-left--9 {
|
||||
position: relative;
|
||||
left: 9px;
|
||||
}
|
||||
.nudge-left--10 {
|
||||
position: relative;
|
||||
left: 10px;
|
||||
}
|
110
ghost/members-api/static/auth/styles/variables.css
Normal file
110
ghost/members-api/static/auth/styles/variables.css
Normal file
|
@ -0,0 +1,110 @@
|
|||
/* Design system variables */
|
||||
/* ------------------------------------------------------------
|
||||
|
||||
Variables to define colors, fonts and various visual elements.
|
||||
Layout use a 4px grid.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/* Colors */
|
||||
/* ------------------------------------------------------------ */
|
||||
:root {
|
||||
/* Base colors */
|
||||
--blue: #3eb0ef;
|
||||
--green: #a4d037;
|
||||
--red: #f05230;
|
||||
--yellow: #fecd35;
|
||||
--white: #ffffff;
|
||||
--grey: #B8C2CC;
|
||||
--black: #22292F;
|
||||
|
||||
/* Variations */
|
||||
--blue-l3: color-mod(var(--blue) l(+15%));
|
||||
--blue-l2: color-mod(var(--blue) l(+10%));
|
||||
--blue-l1: color-mod(var(--blue) l(+5%));
|
||||
--blue-d1: color-mod(var(--blue) l(-5%));
|
||||
--blue-d2: color-mod(var(--blue) l(-10%));
|
||||
--blue-d3: color-mod(var(--blue) l(-15%));
|
||||
|
||||
--green-l3: color-mod(var(--green) l(+15%));
|
||||
--green-l2: color-mod(var(--green) l(+10%));
|
||||
--green-l1: color-mod(var(--green) l(+5%));
|
||||
--green-d1: color-mod(var(--green) l(-5%));
|
||||
--green-d2: color-mod(var(--green) l(-10%));
|
||||
--green-d3: color-mod(var(--green) l(-15%));
|
||||
|
||||
--yellow-l3: color-mod(var(--yellow) l(+15%));
|
||||
--yellow-l2: color-mod(var(--yellow) l(+10%));
|
||||
--yellow-l1: color-mod(var(--yellow) l(+5%));
|
||||
--yellow-d1: color-mod(var(--yellow) l(-5%));
|
||||
--yellow-d2: color-mod(var(--yellow) l(-10%));
|
||||
--yellow-d3: color-mod(var(--yellow) l(-13%));
|
||||
|
||||
--red-l3: color-mod(var(--red) l(+15%));
|
||||
--red-l2: color-mod(var(--red) l(+10%));
|
||||
--red-l1: color-mod(var(--red) l(+5%));
|
||||
--red-d1: color-mod(var(--red) l(-5%));
|
||||
--red-d2: color-mod(var(--red) l(-10%));
|
||||
--red-d3: color-mod(var(--red) l(-15%));
|
||||
|
||||
--grey-l3:#F8FAFC;
|
||||
--grey-l2:#F1F5F8;
|
||||
--grey-l1:#DAE1E7;
|
||||
--grey-d1:#8795A1;
|
||||
--grey-d2:#606F7B;
|
||||
--grey-d3:#3D4852;
|
||||
}
|
||||
|
||||
|
||||
/* Typography */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/* Fonts */
|
||||
:root {
|
||||
--default-font: -apple-system, BlinkMacSystemFont,
|
||||
'avenir next', avenir,
|
||||
'helvetica neue', helvetica,
|
||||
ubuntu,
|
||||
roboto, noto,
|
||||
'segoe ui', arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
/* Type scale */
|
||||
:root {
|
||||
--text-2xs: 1.15rem;
|
||||
--text-xs: 1.3rem;
|
||||
--text-s: 1.4rem;
|
||||
--text-base: 1.5rem;
|
||||
--text-l: 1.8rem;
|
||||
--text-xl: 2.5rem;
|
||||
--text-2xl: 3.0rem;
|
||||
--text-3xl: 3.6rem;
|
||||
--text-4xl: 4.5rem;
|
||||
}
|
||||
|
||||
|
||||
/* Visual elements */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/* Borders */
|
||||
:root {
|
||||
--border-radius-s: 2px;
|
||||
--border-radius-base: 4px;
|
||||
--border-radius-l: 8px;
|
||||
--border-radius-xl: 12px;
|
||||
}
|
||||
|
||||
/* Shadows */
|
||||
:root {
|
||||
--box-shadow-base: 0 0 1px rgba(0,0,0,.12), 0 16px 24px -12px rgba(0,0,0,.2);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
:root {
|
||||
/* Speed (f: faster, s: slower) */
|
||||
--animation-speed-f1: 0.18s;
|
||||
--animation-speed-base: 0.25s;
|
||||
--animation-speed-s1: 0.45s;
|
||||
}
|
8946
ghost/members-api/static/auth/yarn.lock
Normal file
8946
ghost/members-api/static/auth/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
184
ghost/members-api/static/gateway/bundle.js
Normal file
184
ghost/members-api/static/gateway/bundle.js
Normal file
|
@ -0,0 +1,184 @@
|
|||
/* global window document location fetch */
|
||||
(function () {
|
||||
if (window.parent === window) {
|
||||
return;
|
||||
}
|
||||
let storage;
|
||||
try {
|
||||
storage = window.localStorage;
|
||||
} catch (e) {
|
||||
storage = window.sessionStorage;
|
||||
}
|
||||
const origin = new URL(document.referrer).origin;
|
||||
const handlers = {};
|
||||
function addMethod(method, fn) {
|
||||
handlers[method] = function ({uid, options}) {
|
||||
fn(options)
|
||||
.then(function (data) {
|
||||
window.parent.postMessage({uid, data}, origin);
|
||||
})
|
||||
.catch(function (error) {
|
||||
window.parent.postMessage({uid, error: error.message}, origin);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// @TODO this needs to be configurable
|
||||
const membersApi = location.pathname.replace(/\/members\/gateway\/?$/, '/ghost/api/v2/members');
|
||||
function getToken({audience}) {
|
||||
return fetch(`${membersApi}/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
origin,
|
||||
audience: audience || origin
|
||||
})
|
||||
}).then((res) => {
|
||||
if (!res.ok) {
|
||||
if (res.status === 401) {
|
||||
storage.removeItem('signedin');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
storage.setItem('signedin', true);
|
||||
return res.text();
|
||||
});
|
||||
}
|
||||
|
||||
addMethod('init', function init() {
|
||||
if (storage.getItem('signedin')) {
|
||||
window.parent.postMessage({event: 'signedin'}, origin);
|
||||
} else {
|
||||
window.parent.postMessage({event: 'signedout'}, origin);
|
||||
}
|
||||
|
||||
getToken({audience: origin});
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
addMethod('getToken', getToken);
|
||||
|
||||
addMethod('signin', function signin({email, password}) {
|
||||
return fetch(`${membersApi}/signin`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
origin,
|
||||
email,
|
||||
password
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.ok) {
|
||||
storage.setItem('signedin', true);
|
||||
}
|
||||
return res.ok;
|
||||
});
|
||||
});
|
||||
|
||||
addMethod('signup', function signin({name, email, password}) {
|
||||
return fetch(`${membersApi}/signup`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
origin,
|
||||
name,
|
||||
email,
|
||||
password
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.ok) {
|
||||
storage.setItem('signedin', true);
|
||||
}
|
||||
return res.ok;
|
||||
});
|
||||
});
|
||||
|
||||
addMethod('signout', function signout(/*options*/) {
|
||||
return fetch(`${membersApi}/signout`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
origin
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.ok) {
|
||||
storage.removeItem('signedin');
|
||||
}
|
||||
return res.ok;
|
||||
});
|
||||
});
|
||||
|
||||
addMethod('request-password-reset', function signout({email}) {
|
||||
return fetch(`${membersApi}/request-password-reset`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
origin,
|
||||
email
|
||||
})
|
||||
}).then((res) => {
|
||||
return res.ok;
|
||||
});
|
||||
});
|
||||
|
||||
addMethod('reset-password', function signout({token, password}) {
|
||||
return fetch(`${membersApi}/reset-password`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
origin,
|
||||
token,
|
||||
password
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.ok) {
|
||||
storage.setItem('signedin', true);
|
||||
}
|
||||
return res.ok;
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('storage', function (event) {
|
||||
if (event.storageArea !== storage) {
|
||||
return;
|
||||
}
|
||||
const newValue = event.newValue;
|
||||
const oldValue = event.oldValue;
|
||||
if (event.key === 'signedin') {
|
||||
if (newValue && !oldValue) {
|
||||
return window.parent.postMessage({event: 'signedin'}, origin);
|
||||
}
|
||||
if (!newValue && oldValue) {
|
||||
return window.parent.postMessage({event: 'signedout'}, origin);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.origin !== origin) {
|
||||
return;
|
||||
}
|
||||
if (!event.data || !event.data.uid) {
|
||||
return;
|
||||
}
|
||||
if (!handlers[event.data.method]) {
|
||||
return window.parent.postMessage({
|
||||
uid: event.data.uid,
|
||||
error: 'Unknown method'
|
||||
}, origin);
|
||||
}
|
||||
handlers[event.data.method](event.data);
|
||||
});
|
||||
})();
|
1
ghost/members-api/static/gateway/index.html
Normal file
1
ghost/members-api/static/gateway/index.html
Normal file
|
@ -0,0 +1 @@
|
|||
<script src="bundle.js"></script>
|
Loading…
Add table
Reference in a new issue