Signed-off-by: Tomas Slusny <slusnucky@gmail.com>pull/115/head
@@ -1,4 +1,5 @@ | |||
{ | |||
"extends": "react-app", | |||
"settings": { | |||
"react": { | |||
"pragma": "h", |
@@ -0,0 +1,4 @@ | |||
{ | |||
"singleQuote": true, | |||
"semi": false | |||
} |
@@ -1,5 +1,6 @@ | |||
const fs = require('fs') | |||
const path = require('path') | |||
const { injectBabelPlugin } = require('react-app-rewired') | |||
const HtmlWebpackPlugin = require('html-webpack-plugin') | |||
const PrerenderSPAPlugin = require('prerender-spa-plugin') | |||
const SitemapPlugin = require('sitemap-webpack-plugin').default | |||
@@ -61,9 +62,13 @@ const feedMapper = fileName => { | |||
} | |||
} | |||
module.exports = function override (config, env) { | |||
module.exports = function override(config, env) { | |||
config = rewirePreact(config, env) | |||
config = rewireEslint(config, env) | |||
config = injectBabelPlugin( | |||
['babel-plugin-transform-react-jsx', { pragma: 'h', useBuiltIns: true }], | |||
config | |||
) | |||
if (process.env.NODE_ENV !== 'production') { | |||
return config |
@@ -22,32 +22,32 @@ | |||
"s-ago": "^2.0.1" | |||
}, | |||
"devDependencies": { | |||
"babel-eslint": "^9.0.0", | |||
"front-matter": "^2.3.0", | |||
"husky": "^1.0.0-rc.13", | |||
"markdown-with-front-matter-loader": "^0.1.0", | |||
"null-loader": "^0.1.1", | |||
"prerender-spa-plugin": "^3.3.0", | |||
"prettier": "^1.14.2", | |||
"pretty-quick": "^1.6.0", | |||
"react-app-rewire-eslint": "^0.2.3", | |||
"react-app-rewire-preact": "^1.0.1", | |||
"react-app-rewired": "^1.5.2", | |||
"react-scripts": "^1.1.5", | |||
"redux-logger": "^3.0.6", | |||
"serve": "^10.0.0", | |||
"sitemap-webpack-plugin": "^0.5.1", | |||
"standard": "^12.0.1" | |||
"sitemap-webpack-plugin": "^0.5.1" | |||
}, | |||
"scripts": { | |||
"fix": "prettier --single-quote --trailing-comma none --write {*,public/*,src/**/*}.{js,json,css,md} && standard --parser babel-eslint --fix", | |||
"test": "standard --parser babel-eslint && CI=true react-app-rewired test", | |||
"fix": "eslint --fix src && prettier --write {*,public/*,src/**,src/**/*}.{js,json,css,md}", | |||
"test": "react-app-rewired test", | |||
"start": "react-app-rewired start", | |||
"build": "react-app-rewired build", | |||
"now-start": "serve -s ./build" | |||
}, | |||
"standard": { | |||
"env": [ | |||
"jest" | |||
] | |||
"husky": { | |||
"hooks": { | |||
"pre-commit": "pretty-quick --staged" | |||
} | |||
}, | |||
"browserslist": { | |||
"development": [ |
@@ -8,7 +8,7 @@ export default base => { | |||
* @param {object} options fetch options | |||
* @return {Promise} | |||
*/ | |||
async function fetchFunc (url, options) { | |||
async function fetchFunc(url, options) { | |||
const correctedOptions = options || {} | |||
if (options.body) { |
@@ -1,6 +1,8 @@ | |||
import parseBlog from './parse-blog' | |||
const blog = require.context('!null-loader!./_posts', false, /.md$/).keys() | |||
const blog = require | |||
.context('!null-loader!./_posts', false, /.md$/) | |||
.keys() | |||
.sort() | |||
.reverse() | |||
.reduce((memo, fileName) => { | |||
@@ -16,7 +18,9 @@ const blog = require.context('!null-loader!./_posts', false, /.md$/).keys() | |||
} | |||
} | |||
return import(`!markdown-with-front-matter-loader!./_posts/${parsed.file}.md`).then(mapper) | |||
return import(`!markdown-with-front-matter-loader!./_posts/${ | |||
parsed.file | |||
}.md`).then(mapper) | |||
} | |||
return memo.set(parsed.id, resolver) |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import '@fortawesome/fontawesome-free/css/all.min.css' | |||
import 'bootstrap/dist/css/bootstrap.min.css' | |||
import { h } from 'preact' | |||
@@ -19,23 +18,23 @@ const App = ({ loading, stars, navbarDark }) => ( | |||
<Loader loading={loading > 0} /> | |||
<Navigation dark={navbarDark} stars={stars} /> | |||
<Router> | |||
<Redirect path='/discord' to={links.discord} /> | |||
<Async path='/' getComponent={() => import('../routes/home')} /> | |||
<Async path='/blog' getComponent={() => import('../routes/blog')} /> | |||
<Redirect path="/discord" to={links.discord} /> | |||
<Async path="/" getComponent={() => import('../routes/home')} /> | |||
<Async path="/blog" getComponent={() => import('../routes/blog')} /> | |||
<Async | |||
path='/blog/show/:id' | |||
path="/blog/show/:id" | |||
getComponent={() => import('../routes/blog-show')} | |||
/> | |||
<Async | |||
path='/features' | |||
path="/features" | |||
getComponent={() => import('../routes/features')} | |||
/> | |||
<Async | |||
path='/xp/show/:skill/:name/:start/:end' | |||
path="/xp/show/:skill/:name/:start/:end" | |||
getComponent={() => import('../routes/xp-show')} | |||
/> | |||
<Async | |||
path='/logged-in' | |||
path="/logged-in" | |||
getComponent={() => import('../routes/logged-in')} | |||
/> | |||
<Async default getComponent={() => import('../routes/not-found')} /> |
@@ -1,15 +1,14 @@ | |||
/** @jsx h */ | |||
import { h, Component } from 'preact' | |||
class Async extends Component { | |||
constructor () { | |||
constructor() { | |||
super() | |||
this.state = { | |||
componentData: null | |||
} | |||
} | |||
loadComponent () { | |||
loadComponent() { | |||
const componentData = this.props.getComponent() | |||
// In case returned value was a promise | |||
@@ -28,11 +27,11 @@ class Async extends Component { | |||
} | |||
} | |||
componentWillMount () { | |||
componentWillMount() { | |||
this.loadComponent() | |||
} | |||
componentWillReceiveProps (nextProps) { | |||
componentWillReceiveProps(nextProps) { | |||
if (this.props.path && this.props.path !== nextProps.path) { | |||
this.setState( | |||
{ | |||
@@ -45,7 +44,7 @@ class Async extends Component { | |||
} | |||
} | |||
render () { | |||
render() { | |||
if (this.state.componentData) { | |||
if (this.props.path) { | |||
return h(this.state.componentData, this.props) |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
import ago from 's-ago' | |||
@@ -13,14 +12,14 @@ const Commit = ({ url, message, author, date }) => | |||
<a href={author.url ? author.url : url} style={{ color: 'cyan' }}> | |||
{author.avatar ? ( | |||
<span> | |||
<img src={author.avatar} alt='Avatar' class='rounded icon' />{' '} | |||
<img src={author.avatar} alt="Avatar" class="rounded icon" />{' '} | |||
</span> | |||
) : ( | |||
<noscript /> | |||
)} | |||
{author.name} | |||
</a>{' '} | |||
<span class='text-muted'>{ago(date)}</span> | |||
<span class="text-muted">{ago(date)}</span> | |||
</div> | |||
) : ( | |||
<noscript /> |
@@ -1,24 +1,23 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
const Feature = ({ image, title, description, link }) => ( | |||
<div | |||
class='col-xl-3 col-lg-3 col-md-4 col-sm-6 col-xs-12' | |||
class="col-xl-3 col-lg-3 col-md-4 col-sm-6 col-xs-12" | |||
style={{ marginBottom: 15 }} | |||
> | |||
<div class='card'> | |||
<img class='card-img-top' alt={title} src={image} /> | |||
<div class='card-body'> | |||
<h5 class='card-title'> | |||
<div class="card"> | |||
<img class="card-img-top" alt={title} src={image} /> | |||
<div class="card-body"> | |||
<h5 class="card-title"> | |||
{link ? ( | |||
<a href={link} alt='View on Wiki'> | |||
<a href={link} alt="View on Wiki"> | |||
{title} | |||
</a> | |||
) : ( | |||
title | |||
)} | |||
</h5> | |||
<p class='card-text'>{description}</p> | |||
<p class="card-text">{description}</p> | |||
</div> | |||
</div> | |||
</div> |
@@ -1,13 +1,12 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
const Footer = () => ( | |||
<div> | |||
<hr /> | |||
Developed with <i class='fas fa-heart' /> and <i class='fas fa-coffee' />{' '} | |||
using <a href='https://getbootstrap.com/'>Bootstrap</a>,{' '} | |||
<a href='https://reactjs.org/'>React</a> and{' '} | |||
<a href='https://fontawesome.com/'>Font Awesome</a> | |||
Developed with <i class="fas fa-heart" /> and <i class="fas fa-coffee" />{' '} | |||
using <a href="https://getbootstrap.com/">Bootstrap</a>,{' '} | |||
<a href="https://reactjs.org/">React</a> and{' '} | |||
<a href="https://fontawesome.com/">Font Awesome</a> | |||
</div> | |||
) | |||
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import platform from 'platform' | |||
import { h, Component } from 'preact' | |||
import { connect } from 'preact-redux' | |||
@@ -7,7 +6,7 @@ import { bindActionCreators } from 'redux' | |||
import { makeNavbarDark, makeNavbarDefault } from '../modules/app' | |||
import Commit from './commit' | |||
function isOsCorrect (osName) { | |||
function isOsCorrect(osName) { | |||
if (!platform.os.family) { | |||
return false | |||
} | |||
@@ -27,7 +26,7 @@ function isOsCorrect (osName) { | |||
} | |||
class Hero extends Component { | |||
constructor (props) { | |||
constructor(props) { | |||
super(props) | |||
this.updateBackground = this.updateBackground.bind(this) | |||
this.handleScroll = this.handleScroll.bind(this) | |||
@@ -39,7 +38,7 @@ class Hero extends Component { | |||
} | |||
} | |||
updateBackground (index) { | |||
updateBackground(index) { | |||
this.setState({ | |||
...this.state, | |||
image: this.props.images[index], | |||
@@ -47,13 +46,12 @@ class Hero extends Component { | |||
}) | |||
} | |||
handleScroll () { | |||
handleScroll() { | |||
const jumbo = document.getElementById('jumbo') | |||
const jumboBottom = jumbo.offsetTop + jumbo.offsetHeight | |||
const navbar = document.getElementsByClassName('navbar')[0] | |||
const fromTop = jumboBottom - navbar.offsetHeight | |||
const stop = | |||
window.scrollY || window.pageYOffset || document.body.scrollTop | |||
const stop = window.scrollY || window.pageYOffset || document.body.scrollTop | |||
if (this.props.navbarDark) { | |||
if (stop > fromTop) { | |||
@@ -66,7 +64,7 @@ class Hero extends Component { | |||
} | |||
} | |||
componentDidMount () { | |||
componentDidMount() { | |||
// Update background | |||
this.updateBackground(0) | |||
@@ -82,7 +80,7 @@ class Hero extends Component { | |||
document.addEventListener('scroll', this.handleScroll) | |||
} | |||
componentWillUnmount () { | |||
componentWillUnmount() { | |||
// Remove background updater | |||
clearInterval(this.state.interval) | |||
@@ -93,7 +91,7 @@ class Hero extends Component { | |||
document.removeEventListener('scroll', this.handleScroll) | |||
} | |||
render ({ title, description, buttons, release, commit, playing }) { | |||
render({ title, description, buttons, release, commit, playing }) { | |||
let regularButtons = filter(button => !button.dropdown)(buttons) | |||
const dropdownButtons = filter(button => button.dropdown)(buttons) | |||
const defaultDropdownItem = find(button => button.os === 'all')( | |||
@@ -109,35 +107,35 @@ class Hero extends Component { | |||
return ( | |||
<div | |||
class='jumbotron jumbotron-fluid' | |||
class="jumbotron jumbotron-fluid" | |||
style={{ backgroundImage: `url(${this.state.image})` }} | |||
id='jumbo' | |||
id="jumbo" | |||
> | |||
<div class='jumbotron-cell'> | |||
<div class='jumbotron-body'> | |||
<h1 class='display-2'>{title}</h1> | |||
<p class='lead'>{description}</p> | |||
<p class='lead'> | |||
<div class='btn-group dropdown'> | |||
<div class="jumbotron-cell"> | |||
<div class="jumbotron-body"> | |||
<h1 class="display-2">{title}</h1> | |||
<p class="lead">{description}</p> | |||
<p class="lead"> | |||
<div class="btn-group dropdown"> | |||
<a | |||
type='button' | |||
type="button" | |||
class={'btn btn-' + mainDropdownItem.color} | |||
href={mainDropdownItem.link} | |||
> | |||
<i class={mainDropdownItem.icon} /> {mainDropdownItem.text} | |||
</a> | |||
<button | |||
type='button' | |||
type="button" | |||
class={ | |||
'btn dropdown-toggle dropdown-toggle-split btn-' + | |||
mainDropdownItem.color | |||
} | |||
> | |||
<span class='sr-only'>Toggle Dropdown</span> | |||
<span class="sr-only">Toggle Dropdown</span> | |||
</button> | |||
<div class='dropdown-menu' style={{ textShadow: 'none' }}> | |||
<div class="dropdown-menu" style={{ textShadow: 'none' }}> | |||
{dropdownButtons.map(({ link, icon, text }) => ( | |||
<a class='dropdown-item' href={link} native> | |||
<a class="dropdown-item" href={link} native> | |||
<i class={icon} /> {text} | |||
</a> | |||
))} | |||
@@ -146,17 +144,17 @@ class Hero extends Component { | |||
{regularButtons.map(({ link, color, icon, text }) => ( | |||
<span key={link}> | |||
{' '} | |||
<a type='button' class={'btn btn-' + color} href={link}> | |||
<a type="button" class={'btn btn-' + color} href={link}> | |||
<i class={icon} /> {text} | |||
</a> | |||
<br style={{ marginBottom: 10 }} class='d-md-none' /> | |||
<br style={{ marginBottom: 10 }} class="d-md-none" /> | |||
</span> | |||
))} | |||
</p> | |||
<div class='small'> | |||
<div class="small"> | |||
<Commit {...commit} /> | |||
<b>Latest release:</b>{' '} | |||
<a href='#news' style={{ color: 'cyan' }}> | |||
<a href="#news" style={{ color: 'cyan' }}> | |||
{release || 'unknown'} | |||
</a> | |||
<br /> |
@@ -1,11 +1,10 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
import Footer from './footer' | |||
const Layout = ({ children, fullWidth }) => ( | |||
<div | |||
class='container' | |||
id='layout' | |||
class="container" | |||
id="layout" | |||
style={{ maxWidth: fullWidth ? '100%' : '' }} | |||
> | |||
{children} |
@@ -1,10 +1,9 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
import './loader.css' | |||
const Loader = ({ loading }) => ( | |||
<div | |||
class='fixed-top animated loader' | |||
class="fixed-top animated loader" | |||
style={{ display: loading ? 'block' : 'none' }} | |||
/> | |||
) |
@@ -1,7 +1,7 @@ | |||
import { Component } from 'preact' | |||
class Meta extends Component { | |||
componentDidMount () { | |||
componentDidMount() { | |||
const { title, description, author } = this.props | |||
document.querySelector('title').text = title || '' | |||
document | |||
@@ -12,7 +12,7 @@ class Meta extends Component { | |||
.setAttribute('content', author || '') | |||
} | |||
render () { | |||
render() { | |||
return null | |||
} | |||
} |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
import './navigation.css' | |||
import hero from '../_data/hero' | |||
@@ -12,77 +11,77 @@ const Navigation = ({ stars, dark }) => ( | |||
(dark ? 'navbar-dark' : 'navbar-light bg-white') | |||
} | |||
> | |||
<Link class='navbar-brand' activeClassName='active' href='/'> | |||
<img src={hero.logo} class='icon' alt='Logo' /> Home | |||
<Link class="navbar-brand" activeClassName="active" href="/"> | |||
<img src={hero.logo} class="icon" alt="Logo" /> Home | |||
</Link> | |||
<input type='checkbox' id='navbar-check-box' /> | |||
<label for='navbar-check-box' class='navbar-toggler'> | |||
<span class='navbar-toggler-icon' /> | |||
<input type="checkbox" id="navbar-check-box" /> | |||
<label for="navbar-check-box" class="navbar-toggler"> | |||
<span class="navbar-toggler-icon" /> | |||
</label> | |||
<div class='collapse navbar-collapse' id='navbar'> | |||
<ul class='navbar-nav'> | |||
<li class='nav-item'> | |||
<Link class='nav-link' activeClassName='active' href='/features'> | |||
<i class='fas fa-cogs' /> Features | |||
<div class="collapse navbar-collapse" id="navbar"> | |||
<ul class="navbar-nav"> | |||
<li class="nav-item"> | |||
<Link class="nav-link" activeClassName="active" href="/features"> | |||
<i class="fas fa-cogs" /> Features | |||
</Link> | |||
</li> | |||
<li class='nav-item'> | |||
<Link class='nav-link' activeClassName='active' href='/blog'> | |||
<i class='fas fa-newspaper' /> Blog | |||
<li class="nav-item"> | |||
<Link class="nav-link" activeClassName="active" href="/blog"> | |||
<i class="fas fa-newspaper" /> Blog | |||
</Link> | |||
</li> | |||
<li class='nav-item'> | |||
<a class='nav-link' href='https://github.com/runelite/runelite/wiki'> | |||
<i class='fas fa-file-alt' /> Wiki | |||
<li class="nav-item"> | |||
<a class="nav-link" href="https://github.com/runelite/runelite/wiki"> | |||
<i class="fas fa-file-alt" /> Wiki | |||
</a> | |||
</li> | |||
<li class='nav-item dropdown'> | |||
<a class='nav-link dropdown-toggle' id='navbarDropdown'> | |||
<i class='fas fa-font' /> API | |||
<li class="nav-item dropdown"> | |||
<a class="nav-link dropdown-toggle" id="navbarDropdown"> | |||
<i class="fas fa-font" /> API | |||
</a> | |||
<div class='dropdown-menu' aria-labelledby='navbarDropdown'> | |||
<div class="dropdown-menu" aria-labelledby="navbarDropdown"> | |||
<a | |||
class='dropdown-item' | |||
href='https://static.runelite.net/api/runelite-api/' | |||
class="dropdown-item" | |||
href="https://static.runelite.net/api/runelite-api/" | |||
> | |||
RuneLite API | |||
</a> | |||
<a | |||
class='dropdown-item' | |||
href='https://static.runelite.net/api/runelite-client/' | |||
class="dropdown-item" | |||
href="https://static.runelite.net/api/runelite-client/" | |||
> | |||
RuneLite Client API | |||
</a> | |||
</div> | |||
</li> | |||
<li class='nav-item'> | |||
<a class='nav-link' href={links.discord}> | |||
<i class='fab fa-discord' /> Discord | |||
<li class="nav-item"> | |||
<a class="nav-link" href={links.discord}> | |||
<i class="fab fa-discord" /> Discord | |||
</a> | |||
</li> | |||
<li class='nav-item'> | |||
<a class='nav-link' href={links.twitter}> | |||
<i class='fab fa-twitter' /> Twitter | |||
<li class="nav-item"> | |||
<a class="nav-link" href={links.twitter}> | |||
<i class="fab fa-twitter" /> Twitter | |||
</a> | |||
</li> | |||
</ul> | |||
<ul class='navbar-nav ml-auto'> | |||
<li class='nav-item'> | |||
<a class='nav-link' href={links.patreon}> | |||
<i class='fab fa-patreon' /> Become a patron | |||
<ul class="navbar-nav ml-auto"> | |||
<li class="nav-item"> | |||
<a class="nav-link" href={links.patreon}> | |||
<i class="fab fa-patreon" /> Become a patron | |||
</a> | |||
</li> | |||
<li class='nav-item'> | |||
<a class='nav-link' href='https://github.com/runelite'> | |||
<i class='fab fa-github' /> GitHub | |||
<li class="nav-item"> | |||
<a class="nav-link" href="https://github.com/runelite"> | |||
<i class="fab fa-github" /> GitHub | |||
</a> | |||
</li> | |||
<li class='nav-item'> | |||
<li class="nav-item"> | |||
<a | |||
class='nav-link' | |||
href='https://github.com/runelite/runelite/stargazers' | |||
class="nav-link" | |||
href="https://github.com/runelite/runelite/stargazers" | |||
> | |||
<i class='fas fa-star' /> {stars} Stargazers | |||
<i class="fas fa-star" /> {stars} Stargazers | |||
</a> | |||
</li> | |||
</ul> |
@@ -1,12 +1,11 @@ | |||
/** @jsx h */ | |||
import { Component } from 'preact' | |||
export default class Redirect extends Component { | |||
componentWillMount () { | |||
componentWillMount() { | |||
window.location.replace(this.props.to) | |||
} | |||
render () { | |||
render() { | |||
return null | |||
} | |||
} |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import { h, render } from 'preact' | |||
import { Provider } from 'preact-redux' | |||
import App from './components/app' |
@@ -1,4 +1,4 @@ | |||
function createThunkMiddleware () { | |||
function createThunkMiddleware() { | |||
return ({ dispatch, getState }) => next => action => { | |||
if (typeof action === 'function') { | |||
return action(dispatch, getState) |
@@ -132,23 +132,23 @@ const calculateRanksAndExp = collector => (value, key) => { | |||
if (isRank) { | |||
collector[curKey] = curObj | |||
? { | |||
...curObj, | |||
rank: value - curObj.rank | |||
} | |||
...curObj, | |||
rank: value - curObj.rank | |||
} | |||
: { | |||
xp: 0, | |||
rank: value | |||
} | |||
xp: 0, | |||
rank: value | |||
} | |||
} else { | |||
collector[curKey] = curObj | |||
? { | |||
...curObj, | |||
xp: value - curObj.xp | |||
} | |||
...curObj, | |||
xp: value - curObj.xp | |||
} | |||
: { | |||
xp: value, | |||
rank: 0 | |||
} | |||
xp: value, | |||
rank: 0 | |||
} | |||
} | |||
} | |||
@@ -195,9 +195,9 @@ export const ranksSelector = createSelector( | |||
...(collectedSkills[name] | |||
? inverseRank(collectedSkills[name]) | |||
: { | |||
xp: 0, | |||
rank: 0 | |||
}) | |||
xp: 0, | |||
rank: 0 | |||
}) | |||
})) | |||
) | |||
@@ -1,11 +1,9 @@ | |||
module.exports = (file) => { | |||
module.exports = file => { | |||
// Remove cd and extension | |||
file = file.match(/([\w\d-.]+)\.md/)[1] | |||
// Extract year and path | |||
const tokenizedFilename = file.match( | |||
/^(\d{4}-\d{2}-\d{2})-(\d{2}-\d{2})(.*)/ | |||
) | |||
const tokenizedFilename = file.match(/^(\d{4}-\d{2}-\d{2})-(\d{2}-\d{2})(.*)/) | |||
// Validation | |||
if ( |
@@ -1,7 +1,11 @@ | |||
import parseBlog from './parse-blog' | |||
it('parses blog post file name properly', () => { | |||
expect(parseBlog('./src/_posts/2018-05-15-23-10-RuneLite-threatened-to-shutdown.md')).toEqual({ | |||
expect( | |||
parseBlog( | |||
'./src/_posts/2018-05-15-23-10-RuneLite-threatened-to-shutdown.md' | |||
) | |||
).toEqual({ | |||
id: '2018-05-15-RuneLite-threatened-to-shutdown', | |||
file: '2018-05-15-23-10-RuneLite-threatened-to-shutdown', | |||
date: new Date(Date.UTC(2018, 4, 15, 23, 10)) |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
import ago from 's-ago' | |||
import Layout from '../components/layout' | |||
@@ -20,12 +19,12 @@ const BlogShow = ({ id }) => ( | |||
author={author} | |||
/> | |||
<h1>{title}</h1> | |||
<p class='text-muted'> | |||
<p class="text-muted"> | |||
{ago(date)} by {author} | |||
</p> | |||
<hr /> | |||
<div | |||
class='markdown-body' | |||
class="markdown-body" | |||
dangerouslySetInnerHTML={{ __html: body }} | |||
/> | |||
</Layout> |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
import ago from 's-ago' | |||
import Layout from '../components/layout' | |||
@@ -13,7 +12,7 @@ const Blog = () => ( | |||
<Meta title={`Blog - ${hero.title}`} /> | |||
<h1>Blog</h1> | |||
<hr /> | |||
<ul class='list-group'> | |||
<ul class="list-group"> | |||
{Array.from(blog.keys()).map(id => ( | |||
<Async | |||
key={id} | |||
@@ -23,17 +22,17 @@ const Blog = () => ( | |||
.then(({ date, title, description, author }) => ( | |||
<Link | |||
key={id} | |||
class='list-group-item list-group-item-action flex-column align-items-start' | |||
activeClassName='active' | |||
class="list-group-item list-group-item-action flex-column align-items-start" | |||
activeClassName="active" | |||
href={`/blog/show/${id}`} | |||
> | |||
<div class='d-flex w-100 justify-content-between'> | |||
<h5 class='mb-1'>{title || id}</h5> | |||
<small class='text-muted'> | |||
<div class="d-flex w-100 justify-content-between"> | |||
<h5 class="mb-1">{title || id}</h5> | |||
<small class="text-muted"> | |||
{ago(date)} by {author} | |||
</small> | |||
</div> | |||
<p class='mb-1 text-muted'>{description}</p> | |||
<p class="mb-1 text-muted">{description}</p> | |||
</Link> | |||
)) | |||
} |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
import Layout from '../components/layout' | |||
import Feature from '../components/feature' | |||
@@ -11,7 +10,7 @@ const Features = () => ( | |||
<Meta title={`Features - ${hero.title}`} /> | |||
<h1>Features</h1> | |||
<hr /> | |||
<div class='row'> | |||
<div class="row"> | |||
{features.map(feature => ( | |||
<Feature key={feature.title} {...feature} /> | |||
))} |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import { Component, h } from 'preact' | |||
import { connect } from 'preact-redux' | |||
import Feature from '../components/feature' | |||
@@ -22,13 +21,13 @@ import { Link } from 'preact-router' | |||
import Async from '../components/async' | |||
class Home extends Component { | |||
componentDidMount () { | |||
componentDidMount() { | |||
this.props.getCommits() | |||
this.props.getReleases().then(() => this.props.getSessionCount()) | |||
this.props.getRepository() | |||
} | |||
render ({ commit, release, stars, sessionCount }) { | |||
render({ commit, release, stars, sessionCount }) { | |||
return ( | |||
<div style={{ height: 'inherit' }}> | |||
<Meta | |||
@@ -45,12 +44,12 @@ class Home extends Component { | |||
<Layout> | |||
<h1> | |||
Features{' '} | |||
<Link href='/features' style={{ fontSize: 18 }}> | |||
<Link href="/features" style={{ fontSize: 18 }}> | |||
See all features... | |||
</Link> | |||
</h1> | |||
<hr /> | |||
<div class='row'> | |||
<div class="row"> | |||
{features | |||
.filter(feature => feature.home) | |||
.map(({ image, title }) => ({ image, title })) | |||
@@ -58,9 +57,9 @@ class Home extends Component { | |||
<Feature key={feature.title} {...feature} /> | |||
))} | |||
</div> | |||
<h1 id='news'> | |||
<h1 id="news"> | |||
Latest news{' '} | |||
<Link href='/blog' style={{ fontSize: 18 }}> | |||
<Link href="/blog" style={{ fontSize: 18 }}> | |||
See all news... | |||
</Link> | |||
</h1> | |||
@@ -69,7 +68,7 @@ class Home extends Component { | |||
getComponent={() => | |||
latest().then(({ body }) => ( | |||
<div | |||
class='markdown-body' | |||
class="markdown-body" | |||
dangerouslySetInnerHTML={{ __html: body }} | |||
/> | |||
)) |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
import Layout from '../components/layout' | |||
import hero from '../_data/hero' |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import { h } from 'preact' | |||
import hero from '../_data/hero' | |||
import Meta from '../components/meta' | |||
@@ -31,8 +30,8 @@ const NotFound = () => ( | |||
}} | |||
> | |||
<h1>404</h1> | |||
<p class='lead'> | |||
Sorry, page was not found Click <Link href='/'>here</Link> to return | |||
<p class="lead"> | |||
Sorry, page was not found Click <Link href="/">here</Link> to return | |||
to home page. | |||
</p> | |||
</div> |
@@ -1,4 +1,3 @@ | |||
/** @jsx h */ | |||
import 'chartist/dist/chartist.min.css' | |||
import { h, Component } from 'preact' | |||
import { connect } from 'preact-redux' | |||
@@ -22,11 +21,11 @@ import hero from '../_data/hero' | |||
import skills from '../_data/skills' | |||
import Meta from '../components/meta' | |||
function isNumeric (value) { | |||
function isNumeric(value) { | |||
return !isNaN(value - parseFloat(value)) | |||
} | |||
function parseDate (date, from) { | |||
function parseDate(date, from) { | |||
if (date === 'now') { | |||
date = new Date() | |||
} else if (!isNumeric(date)) { | |||
@@ -43,15 +42,14 @@ function parseDate (date, from) { | |||
const capitalizeFirstLetter = string => | |||
string.charAt(0).toUpperCase() + string.slice(1) | |||
const numberWithCommas = x => | |||
x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') | |||
const numberWithCommas = x => x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') | |||
const createValueBadge = (value, suffix) => | |||
value >= 0 ? ( | |||
<span class='badge badge-success'> | |||
<span class="badge badge-success"> | |||
+{numberWithCommas(value)} {suffix} | |||
</span> | |||
) : ( | |||
<span class='badge badge-danger'> | |||
<span class="badge badge-danger"> | |||
{numberWithCommas(value)} {suffix} | |||
</span> | |||
) | |||
@@ -59,7 +57,7 @@ const createValueBadge = (value, suffix) => | |||
const safeDate = date => date || new Date() | |||
class XpShow extends Component { | |||
constructor (props) { | |||
constructor(props) { | |||
super(props) | |||
this.state = { | |||
@@ -72,7 +70,7 @@ class XpShow extends Component { | |||
} | |||
} | |||
componentDidMount () { | |||
componentDidMount() { | |||
const startDate = safeDate(parseDate(this.props.start, new Date())) | |||
const endDate = safeDate(parseDate(this.props.end, startDate)) | |||
@@ -157,35 +155,35 @@ class XpShow extends Component { | |||
) | |||
} | |||
componentWillReceiveProps ({ skillRank, skillXp, allRanks, allXp }) { | |||
componentWillReceiveProps({ skillRank, skillXp, allRanks, allXp }) { | |||
this.state.skillRank.update(skillRank) | |||
this.state.skillXp.update(skillXp) | |||
this.state.allRanks.update(allRanks) | |||
this.state.allXp.update(allXp) | |||
} | |||
componentWillUnmount () { | |||
componentWillUnmount() { | |||
this.state.skillRank.detach() | |||
this.state.skillXp.detach() | |||
this.state.allRanks.detach() | |||
this.state.allXp.detach() | |||
} | |||
render ({ name, skill, ranks }) { | |||
render({ name, skill, ranks }) { | |||
return ( | |||
<Layout> | |||
<Meta title={`Experience Tracker - ${hero.title}`} /> | |||
<h1> | |||
{name} /{' '} | |||
<small class='text-muted'> | |||
<small class="text-muted"> | |||
{skill} / {this.state.startDate.toDateString().toLowerCase()} /{' '} | |||
{this.state.endDate.toDateString().toLowerCase()} | |||
</small> | |||
</h1> | |||
<hr /> | |||
<div class='row'> | |||
<div class='col-xl-3 col-md-4 col-sm-12 col-xs-12'> | |||
<ul class='list-group'> | |||
<div class="row"> | |||
<div class="col-xl-3 col-md-4 col-sm-12 col-xs-12"> | |||
<ul class="list-group"> | |||
{ranks.map(({ skill: playerSkill, rank, xp }) => ( | |||
<Link | |||
class={ | |||
@@ -199,33 +197,33 @@ class XpShow extends Component { | |||
alt={playerSkill} | |||
src={`/img/skillicons/${playerSkill}.png`} | |||
/>{' '} | |||
<span class='d-md-none d-lg-inline'> | |||
<span class="d-md-none d-lg-inline"> | |||
{capitalizeFirstLetter(playerSkill)} | |||
</span> | |||
<span class='float-right'> | |||
<span class="float-right"> | |||
{createValueBadge(rank, '')} {createValueBadge(xp, 'xp')} | |||
</span> | |||
</Link> | |||
))} | |||
</ul> | |||
</div> | |||
<div class='col-xl-9 col-md-8 col-sm-12 col-xs-12'> | |||
<div class="col-xl-9 col-md-8 col-sm-12 col-xs-12"> | |||
<h5> | |||
<small>Total experience gained</small> | |||
</h5> | |||
<div id='all-xp' class='ct-chart' style={{ height: 175 }} /> | |||
<div id="all-xp" class="ct-chart" style={{ height: 175 }} /> | |||
<h5> | |||
<small>Total ranks gained</small> | |||
</h5> | |||
<div id='all-ranks' class='ct-chart' style={{ height: 175 }} /> | |||
<div id="all-ranks" class="ct-chart" style={{ height: 175 }} /> | |||
<h5> | |||
<small>{capitalizeFirstLetter(skill)} ranks</small> | |||
</h5> | |||
<div id='skill-rank' class='ct-chart' style={{ height: 375 }} /> | |||
<div id="skill-rank" class="ct-chart" style={{ height: 375 }} /> | |||
<h5> | |||
<small>{capitalizeFirstLetter(skill)} experience</small> | |||
</h5> | |||
<div id='skill-xp' class='ct-chart' style={{ height: 375 }} /> | |||
<div id="skill-xp" class="ct-chart" style={{ height: 375 }} /> | |||
</div> | |||
</div> | |||
</Layout> |