Commit 69c6aa81 authored by Yamagishi Kazutoshi's avatar Yamagishi Kazutoshi

Introduce GitLab CI

parent b067261e
Pipeline #19 failed with stages
in 0 seconds
...@@ -10,6 +10,18 @@ module.exports = { ...@@ -10,6 +10,18 @@ module.exports = {
}, },
rules: { rules: {
'jsx-quotes': ['error', 'prefer-single'], 'jsx-quotes': ['error', 'prefer-single'],
'max-len': [
'error',
256,
2,
{
ignoreUrls: true,
ignoreComments: false,
ignoreRegExpLiterals: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
},
],
'no-multi-spaces': [ 'no-multi-spaces': [
'error', 'error',
{ {
...@@ -19,5 +31,13 @@ module.exports = { ...@@ -19,5 +31,13 @@ module.exports = {
}, },
}, },
], ],
'space-before-function-paren': [
'error',
{
anonymous: 'always',
named: 'always',
asyncArrow: 'always',
},
],
}, },
}; };
image: circleci/ruby:2.5.1-stretch-node
.default-cache: &default-cache
paths:
- ./.yarn-cache/
- ./node_modules/
- ./vendor/bundle/
variables:
RAILS_ENV: test
NODE_ENV: test
stages:
- prepare
- build
- review
before_script:
- bundle install --clean --deployment --jobs 4 --retry 3 --without development production
- yarn install --frozen-lockfile --cache-folder ./.yarn-cache/
setup-test-env:
stage: prepare
cache:
<<: *default-cache
script:
- ruby -v
- node -v
rubocop:
stage: review
dependencies:
- setup-test-env
cache:
<<: *default-cache
script:
- bundle exec rubocop
i18n-tasks:
stage: review
dependencies:
- setup-test-env
cache:
<<: *default-cache
script:
- bundle exec i18n-tasks check-normalized
- bundle exec i18n-tasks unused
eslint:
stage: review
dependencies:
- setup-test-env
cache:
<<: *default-cache
script:
- ./bin/rails react_on_rails:locale
- yarn test:lint
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import domainShape from '../types/domainShape';
const ConnectPrompt = ({ isPioneer, domains }) => ( const ConnectPrompt = ({ isPioneer, domains }) => (
<div className='connect-prompt'> <div className='connect-prompt'>
...@@ -20,15 +21,13 @@ const ConnectPrompt = ({ isPioneer, domains }) => ( ...@@ -20,15 +21,13 @@ const ConnectPrompt = ({ isPioneer, domains }) => (
</div> </div>
); );
ConnectPrompt.defaultProps = {
isPioneer: false,
};
ConnectPrompt.propTypes = { ConnectPrompt.propTypes = {
isPioneer: PropTypes.bool, isPioneer: PropTypes.bool,
domains: PropTypes.arrayOf(PropTypes.shape({ domains: PropTypes.arrayOf(domainShape).isRequired,
uri: PropTypes.string, };
})).isRequired,
ConnectPrompt.defaultProps = {
isPioneer: false,
}; };
export default ConnectPrompt; export default ConnectPrompt;
...@@ -2,19 +2,24 @@ import PropTypes from 'prop-types'; ...@@ -2,19 +2,24 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { Motion, StaggeredMotion, spring, presets } from 'react-motion'; import { Motion, StaggeredMotion, spring, presets } from 'react-motion';
import { FormattedMessage, FormattedNumber } from 'react-intl'; import { FormattedMessage, FormattedNumber } from 'react-intl';
import domainShape from '../types/domainShape';
import ConnectPrompt from './ConnectPrompt'; import ConnectPrompt from './ConnectPrompt';
export default class HelloWorld extends React.PureComponent { export default class HelloWorld extends React.PureComponent {
static propTypes = { static propTypes = {
total: PropTypes.number, total: PropTypes.number,
at: PropTypes.number, at: PropTypes.number,
results: PropTypes.array.isRequired, results: PropTypes.arrayOf(PropTypes.any).isRequired,
domains: PropTypes.array.isRequired, domains: PropTypes.arrayOf(domainShape).isRequired,
inProgress: PropTypes.bool.isRequired, inProgress: PropTypes.bool.isRequired,
fetchProgress: PropTypes.func.isRequired, fetchProgress: PropTypes.func.isRequired,
fetchResults: PropTypes.func.isRequired,
mastodonIsConnected: PropTypes.bool.isRequired, mastodonIsConnected: PropTypes.bool.isRequired,
defaultDomains: PropTypes.array.isRequired, defaultDomains: PropTypes.arrayOf(PropTypes.string).isRequired,
};
static defaultProps = {
total: 0,
at: 0,
}; };
componentDidMount () { componentDidMount () {
...@@ -23,7 +28,15 @@ export default class HelloWorld extends React.PureComponent { ...@@ -23,7 +28,15 @@ export default class HelloWorld extends React.PureComponent {
} }
render () { render () {
const { inProgress, at, total, results, domains, defaultDomains, mastodonIsConnected } = this.props; const {
inProgress,
at,
total,
results,
domains,
defaultDomains,
mastodonIsConnected,
} = this.props;
if (inProgress) { if (inProgress) {
const pct = total > 0 ? (at / total).toFixed(2) * 100 : 10; const pct = total > 0 ? (at / total).toFixed(2) * 100 : 10;
...@@ -61,54 +74,71 @@ export default class HelloWorld extends React.PureComponent { ...@@ -61,54 +74,71 @@ export default class HelloWorld extends React.PureComponent {
return ( return (
<div> <div>
{mastodonIsConnected ? (<div className='page-heading'> {mastodonIsConnected ? (
<FormattedMessage id='friends.headline' defaultMessage='Your friends'> <div className='page-heading'>
{text => ( <FormattedMessage id='friends.headline' defaultMessage='Your friends'>
<h3> {text => (
{text} <h3>
<FormattedMessage id='friends.subheadline' defaultMessage='Here are your Twitter friends who are on Mastodon:' tagName='small' /> {text}
</h3> <FormattedMessage id='friends.subheadline' defaultMessage='Here are your Twitter friends who are on Mastodon:' tagName='small' />
</h3>
)}
</FormattedMessage>
</div>
) : <ConnectPrompt domains={defaultDomains} />}
{results.length === 0 && (
<p className='lead'>
<FormattedMessage id='friends.empty' defaultMessage='Right now, there are no results to be shown here. But maybe your friends haven&quot;t used this tool yet! Or maybe you are the trendsetter!' />
</p>
)}
{results.length > 0 && mastodonIsConnected && (
<div style={{ textAlign: 'center', marginTop: -10, marginBottom: 20 }}>
<a className='candy-button' href='/friends/follow_all' data-method='post'>
<FormattedMessage id='friends.follow_all' defaultMessage='Follow all friends on Mastodon' />
</a>
</div>
)}
{results.length > 0 && (
<StaggeredMotion
defaultStyles={results.map(() => ({ scale: 0 }))}
styles={prevInterpolatedStyles => prevInterpolatedStyles.map((_, i) => ({ scale: spring(i === 0 ? 1 : prevInterpolatedStyles[i - 1].scale, presets.gentle) }))}
>
{interpolatingStyles => (
<div className='grid'>
{interpolatingStyles.map((style, i) => (
<a
rel='noopener noreferrer'
target='_blank'
href={results[i].mastodon_url}
style={{ pointerEvents: style.scale === 1 ? 'auto' : 'none', transformOrigin: 'center center', transform: `scale(${style.scale})` }}
key={results[i].mastodon_username}
className='user-card'
title={`@${results[i].twitter_username} on Twitter`}
>
<div className='avatar'><img src={results[i].avatar_url} alt='' /></div>
{results[i].following && (
<div className='following-indicator'>
<i className='fa fa-check' />
</div>
)}
<div className='name'>
<span className='display-name'>{results[i].display_name}</span>
<span className='username'>@{results[i].mastodon_username}</span>
</div>
</a>
))}
</div>
)} )}
</FormattedMessage> </StaggeredMotion>
</div>) : <ConnectPrompt domains={defaultDomains} />} )}
{results.length === 0 && <p className='lead'>
<FormattedMessage id='friends.empty' defaultMessage={`Right now, there are no results to be shown here. But maybe your friends haven't used this tool yet! Or maybe you are the trendsetter!`} />
</p>}
{results.length > 0 && mastodonIsConnected && <div style={{ textAlign: 'center', marginTop: -10, marginBottom: 20 }}>
<a className='candy-button' href='/friends/follow_all' data-method='post'>
<FormattedMessage id='friends.follow_all' defaultMessage='Follow all friends on Mastodon' />
</a>
</div>}
{results.length > 0 && <StaggeredMotion defaultStyles={results.map(_ => ({ scale: 0 }))} styles={prevInterpolatedStyles => prevInterpolatedStyles.map((_, i) => {
return i == 0
? { scale: spring(1, presets.gentle) }
: { scale: spring(prevInterpolatedStyles[i - 1].scale, presets.gentle) };
})}>
{interpolatingStyles => (
<div className='grid'>
{interpolatingStyles.map((style, i) => (
<a target='_blank' href={results[i].mastodon_url} style={{ pointerEvents: style.scale == 1 ? 'auto' : 'none', transformOrigin: 'center center', transform: `scale(${style.scale})` }} key={results[i].mastodon_username} className='user-card' title={`@${results[i].twitter_username} on Twitter`}>
<div className='avatar'><img src={results[i].avatar_url} /></div>
{results[i].following && <div className='following-indicator'>
<i className='fa fa-check' />
</div>}
<div className='name'>
<span className='display-name'>{results[i].display_name}</span>
<span className='username'>@{results[i].mastodon_username}</span>
</div>
</a>
))}
</div>
)}
</StaggeredMotion>}
<div className='page-heading'> <div className='page-heading'>
<FormattedMessage id='friends.your_friends_instance.headline' defaultMessage={`Your friends' instances`}> <FormattedMessage id='friends.your_friends_instance.headline' defaultMessage='Your friends&quot; instances'>
{text => ( {text => (
<h3> <h3>
{text} {text}
...@@ -120,7 +150,7 @@ export default class HelloWorld extends React.PureComponent { ...@@ -120,7 +150,7 @@ export default class HelloWorld extends React.PureComponent {
<div className='grid' id='domains'> <div className='grid' id='domains'>
{(domains.length > 0 ? domains : defaultDomains).map(domain => ( {(domains.length > 0 ? domains : defaultDomains).map(domain => (
<a target='_blank' className='instance-card' href={`https://${domain.uri}/about`} key={domain.uri} style={{ backgroundImage: `url(${domain.thumbnail})` }}> <a rel='noopener noreferrer' target='_blank' className='instance-card' href={`https://${domain.uri}/about`} key={domain.uri} style={{ backgroundImage: `url(${domain.thumbnail})` }}>
<div className='info'> <div className='info'>
<span className='title'>{domain.title}</span> <span className='title'>{domain.title}</span>
<span className='uri'>{domain.uri}</span> <span className='uri'>{domain.uri}</span>
...@@ -130,7 +160,7 @@ export default class HelloWorld extends React.PureComponent { ...@@ -130,7 +160,7 @@ export default class HelloWorld extends React.PureComponent {
</a> </a>
))} ))}
<a target='_blank' className='instance-card' href='https://joinmastodon.org/#getting-started'> <a rel='noopener noreferrer' target='_blank' className='instance-card' href='https://joinmastodon.org/#getting-started'>
<div className='info'> <div className='info'>
<FormattedMessage id='friends.find_more' defaultMessage='Find more on'> <FormattedMessage id='friends.find_more' defaultMessage='Find more on'>
{text => <span className='title'>{text}</span>} {text => <span className='title'>{text}</span>}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment