Browse Source

Add new website

Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
pull/3/head
Tomas Slusny 2 years ago
commit
a4dd0f9f14

+ 21
- 0
LICENSE View File

@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 Tomas Slusny

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

+ 55
- 0
README.md View File

@@ -0,0 +1,55 @@
# redux-boot

React and Redux simple boilerplate

## Local development

To start local server, simply run

```
npm start
```

from console. Your app should be now running on `http://localhost:3000` and you
will be able to see it from your web browser. To debug Redux actions, navigate
to `http://remotedev.io/local/` and you will be able to see all your Redux
actions.

## Publishing to GitHub pages

The deployement to GitHub pages is done automatically using [Travis CI GitHub
pages deployement](https://docs.travis-ci.com/user/deployment/pages).
First read the guide and set up GITHUB_TOKEN so the travis deployement will
work.

If you are deploying to a GitHub user page instead of a project page you'll need
to make two additional modifications:

First, change your repository's source branch to be any branch other than
master.
Additionally, tweak your .travis.yml scripts to push deployments to master:
```diff
deploy:
provider: pages
skip_cleanup: true
github_token: $GITHUB_TOKEN # Set in travis-ci.org dashboard
local_dir: "build"
- target_branch: "gh-pages"
+ target_branch: master
on:
- branch: master
+ branch: "my-branch"
```

Note that if you are setting up a Project Pages site and not using a custom
domain (i.e. your site's address is `username.github.io/repo-name`), then you need
to set `segmentCount` to `1` in the `public/404.html` file in order to keep `/repo-name` in the
path after the redirect.

Finally, make sure GitHub Pages option in your GitHub project settings is set to
use the gh-pages branch:

<img src="http://i.imgur.com/HUjEr9l.png" width="500" alt="gh-pages branch setting">

After all above setup is done, Travis CI should automatically deploy your site
after you push commit.

+ 14071
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 47
- 0
package.json View File

@@ -0,0 +1,47 @@
{
"name": "redux-boot",
"version": "0.0.1",
"private": true,
"dependencies": {
"@fortawesome/fontawesome": "^1.0.1",
"@fortawesome/fontawesome-free-brands": "^5.0.1",
"@fortawesome/fontawesome-free-solid": "^5.0.1",
"@fortawesome/react-fontawesome": "0.0.16",
"bootstrap": "^4.0.0-beta",
"github-markdown-css": "^2.9.0",
"history": "^4.7.2",
"js-cookie": "^2.1.4",
"qs": "^6.5.0",
"ramda": "^0.24.1",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-helmet": "^5.2.0",
"react-redux": "^5.0.6",
"react-redux-loading-bar": "^3.0.3",
"react-timeago": "^3.4.3",
"react-transition-group": "^1.2.0",
"reactstrap": "^5.0.0-alpha.3",
"redux": "^3.7.2",
"redux-actions": "^2.2.1",
"redux-first-router": "0.0.16-next",
"redux-first-router-link": "^1.4.1",
"redux-routines": "^2.0.0-0",
"reselect": "^3.0.1"
},
"devDependencies": {
"eslint": "^4.5.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-react": "^7.3.0",
"markdown-with-front-matter-loader": "^0.1.0",
"react-scripts": "1.0.12",
"remote-redux-devtools": "^0.5.12",
"standard": "^10.0.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "standard && react-scripts test --env=jsdom",
"lint": "standard",
"eject": "react-scripts eject"
}
}

+ 38
- 0
public/404.html View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Single Page Apps for GitHub Pages</title>
<script type="text/javascript">
// Single Page Apps for GitHub Pages
// https://github.com/rafrex/spa-github-pages
// Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License
// ----------------------------------------------------------------------
// This script takes the current url and converts the path and query
// string into just a query string, and then redirects the browser
// to the new url with only a query string and hash fragment,
// e.g. http://www.foo.tld/one/two?a=b&c=d#qwe, becomes
// http://www.foo.tld/?p=/one/two&q=a=b~and~c=d#qwe
// Note: this 404.html file must be at least 512 bytes for it to work
// with Internet Explorer (it is currently > 512 bytes)
// If you're creating a Project Pages site and NOT using a custom domain,
// then set segmentCount to 1 (enterprise users may need to set it to > 1).
// This way the code will only replace the route part of the path, and not
// the real directory in which the app resides, for example:
// https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes
// https://username.github.io/repo-name/?p=/one/two&q=a=b~and~c=d#qwe
// Otherwise, leave segmentCount as 0.
var segmentCount = 0;
var l = window.location;
l.replace(
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
l.pathname.split('/').slice(0, 1 + segmentCount).join('/') + '/?p=/' +
l.pathname.slice(1).split('/').slice(segmentCount).join('/').replace(/&/g, '~and~') +
(l.search ? '&q=' + l.search.slice(1).replace(/&/g, '~and~') : '') +
l.hash
);
</script>
</head>
<body>
</body>
</html>

+ 1
- 0
public/CNAME View File

@@ -0,0 +1 @@
runelite.net

BIN
public/favicon.ico View File


BIN
public/img/cat.jpg View File


BIN
public/img/features/attackstyles.png View File


BIN
public/img/features/boosts.png View File


BIN
public/img/features/grounditems.png View File


BIN
public/img/features/mousehighlight.png View File


BIN
public/img/fullpic.png View File


BIN
public/img/logo.png View File


+ 84
- 0
public/index.html View File

@@ -0,0 +1,84 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<meta name="google-site-verification" content="49F0GuHeg2-31zrlhD7i4qibZb3cX520sskbF9JNUbk" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
small hack to allow us having full screen images/backgrounds etc
-->
<style type="text/css">
html,
body,
#root,
#root > div {
height: 100%!important;
}
</style>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>RuneLite</title>

<!-- Start Single Page Apps for GitHub Pages -->
<script type="text/javascript">
// Single Page Apps for GitHub Pages
// https://github.com/rafrex/spa-github-pages
// Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License
// ----------------------------------------------------------------------
// This script checks to see if a redirect is present in the query string
// and converts it back into the correct url and adds it to the
// browser's history using window.history.replaceState(...),
// which won't cause the browser to attempt to load the new url.
// When the single page app is loaded further down in this file,
// the correct url will be waiting in the browser's history for
// the single page app to route accordingly.
(function(l) {
if (l.search) {
var q = {};
l.search.slice(1).split('&').forEach(function(v) {
var a = v.split('=');
q[a[0]] = a.slice(1).join('=').replace(/~and~/g, '&');
});
if (q.p !== undefined) {
window.history.replaceState(null, null,
l.pathname.slice(0, -1) + (q.p || '') +
(q.q ? ('?' + q.q) : '') +
l.hash
);
}
}
}(window.location))
</script>
<!-- End Single Page Apps for GitHub Pages -->
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

+ 15
- 0
public/manifest.json View File

@@ -0,0 +1,15 @@
{
"short_name": "RuneLite",
"name": "RuneLite",
"icons": [
{
"src": "favicon.ico",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

+ 29
- 0
src/_data/features.js View File

@@ -0,0 +1,29 @@
export default [
{
image: '/img/features/attackstyles.png',
title: 'Attack style',
subtitle: 'Attack style indicator and hiding.',
description: '...',
home: true
},
{
image: '/img/features/grounditems.png',
title: 'Ground items overlay',
subtitle: 'Filter and color items by name and price.',
description: '...',
home: true
},
{
image: '/img/features/boosts.png',
title: 'Detailed information widgets',
subtitle: 'For times and other useful informations.',
description: '...',
home: true
},
{
image: '/img/features/mousehighlight.png',
title: 'Mouse highlighting',
subtitle: 'Highlights content under your mouse cursor',
description: '...'
}
]

+ 4
- 0
src/_data/git.js View File

@@ -0,0 +1,4 @@
export default {
user: 'runelite',
repository: 'runelite'
}

+ 32
- 0
src/_data/hero.js View File

@@ -0,0 +1,32 @@
import { faCoffee } from '@fortawesome/fontawesome-free-solid'
import { faWindows, faGithub } from '@fortawesome/fontawesome-free-brands'

export default {
title: 'RuneLite',
logo: '/img/logo.png',
description: `
RuneLite is a free, open-source and super fast client for Old School
RuneScape. You can download the RuneLite launcher for various platforms below
or contribute to the project on GitHub.
`,
buttons: [
{
link: 'https://github.com/runelite/launcher/releases/download/1.2.1/Runelite.exe',
icon: faWindows,
text: 'Download for Windows',
color: 'primary'
},
{
link: 'https://github.com/runelite/launcher/releases/download/1.2.1/runelite.jar',
icon: faCoffee,
text: 'Download for all platforms',
color: 'success'
},
{
link: 'https://github.com/runelite/',
icon: faGithub,
text: 'View on GitHub',
color: 'secondary'
}
]
}

+ 21
- 0
src/_posts/2017-12-13-New-Site.md View File

@@ -0,0 +1,21 @@
---
title: 'New Site'
description: 'We created new website for RuneLite replacing old one generated by GitHub pages'
---

So, I am happy to present you the new site. This site replaces the old one, what
was generated by GitHub pages.

This new site is using latest web development technologies, focusing on
performance, user-friendliness and responsivity. It is still new, so if you will
have any problems with the site, feel free to submit issue on it's
[GitHub repository](https://github.com/runelite/runelite.net/issues).

Also, if you wan't to contribute, it has never been easier, as the individual
blog posts are rendered from Markdown, so all you need to do to create new blog
post is to submit Pull Request to the repository with new Markdown file in
format `YYYY-DD-MM-My-Post-Title.md` in the `src/_posts` directory.

Well, all I can say now is, enjoy!

~ Tomas

+ 193
- 0
src/api.js View File

@@ -0,0 +1,193 @@
import R from 'ramda'
import Cookie from 'js-cookie'
import {stringify} from 'qs'

const createToken = (response) => ({
user: response.user,
expiresIn: new Date().getTime() + response.expires_in * 1000,
accessToken: response.access_token,
refreshToken: response.refresh_token
})

const setToken = (token) => {
Cookie.set('token', token)
}

const getToken = () => {
return Cookie.getJSON('token')
}

const getAccessToken = () => {
const token = getToken()
return token ? token.accessToken : undefined
}

const getRefreshToken = () => {
const token = getToken()
return token ? token.refreshToken : undefined
}

const injectBaseHeaders = (options) => {
const result = options || {}

if (options.body) {
result.body = JSON.stringify(options.body)
}

result.headers = {
...(options.headers || {}),
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}

return result
}

const injectTokenToUrl = (url, accessToken) => {
if (!accessToken) {
return url
}

const data = {
access_token: accessToken
}

return `${url}/?${stringify(data)}`
}

/**
* Logouts user
*/
function logout () {
if (getToken()) {
Cookie.remove('token')
return true
}

return false
}

/**
* Wrap promise, in case of failure run error action
*
* @param {object} dispatch redux dispatcher
* @param {Promise} promise js promise
* @return {Promise}
*/
async function wrapFailure (dispatch, promise, errorAction) {
try {
const result = await promise
return result
} catch (error) {
if (errorAction) dispatch(errorAction(error))
throw error
}
}

export default (base) => {
/**
* Logins user
*
* @param {object} object containing username and password
* @return {Promise}
*/
async function login ({username, password}) {
logout()

const query = {
username,
password,
grant_type: 'password',
scope: 'read'
}

const response = await fetch(`oauth/token?${stringify(query)}`, {
method: 'POST',
headers: {
authorization: 'Basic c2NhZmZvbGQtY2xpZW50OnNlY3JldA=='
}
})

const newToken = createToken({
...response,
user: username
})
setToken(newToken)
return newToken
}

/**
* Re-logs using refresh token
*
* @return {Promise}
*/
async function refresh () {
const token = getToken()

if (!token) {
throw new Error('No token found')
}

logout()

const query = {
grant_type: 'refresh_token',
refresh_token: token.refreshToken
}

const response = await fetch(`oauth/token?${stringify(query)}`, {
crossDomain: true,
method: 'POST',
headers: {
authorization: 'Basic c2NhZmZvbGQtY2xpZW50OnNlY3JldA=='
}
})

const newToken = createToken({
...response,
user: token.user
})
setToken(newToken)
return token
}

/**
* Fetch data, injecting oauth authorization in process
*
* @param {string} url url
* @param {object} options fetch options
* @return {Promise}
*/
async function fetch (url, options) {
const correctedOptions = injectBaseHeaders(options)
const correctedUrl = `${base}${url}`
const buildRequest = () => {
const accessToken = getAccessToken()
return window.fetch(injectTokenToUrl(correctedUrl, accessToken), correctedOptions)
}

let response = await buildRequest()

if (!response.ok) {
if (response.status === 401 && !!getRefreshToken()) {
const refreshResponse = await refresh()
console.log(refreshResponse)
response = await buildRequest()
} else {
throw new Error(response.statusText)
}
}

const headers = response.headers.get('Content-Type')
const isJson = headers && R.contains('json', headers)
response = isJson ? await response.json() : await response.text()

if (response.error) {
throw new Error(response.error)
}

return response
}

return { login, logout, refresh, fetch, wrapFailure }
}

+ 21
- 0
src/blog.js View File

@@ -0,0 +1,21 @@
const webpackRequireContext = require.context('!markdown-with-front-matter-loader!./_posts', false, /.md$/)

const blog = webpackRequireContext.keys().sort().reverse().reduce((memo, fileName) => {
// frontmatter and content (actual markdown is loaded on '__content', frontmatter is right on root)
const frontMatterMarkdown = webpackRequireContext(fileName)
// remove cd and extension
fileName = fileName.match(/\.\/([^.]+)\.*/)[1]
// extract year and path
let tokenizedFilename = fileName.match(/^(\d{4}-\d{2}-\d{2})(.*)/)
// validation
if (!tokenizedFilename && !tokenizedFilename[1]) throw new Error('no ^YYYY-MM-DD date in blog filename')

let date = tokenizedFilename[1]
let name = tokenizedFilename[2]
let path = date + name

return memo.set(path, Object.assign({date: date}, frontMatterMarkdown))
}, new Map())

export const getLatest = () => blog.values().next().value
export default blog

+ 26
- 0
src/components/app.js View File

@@ -0,0 +1,26 @@
import 'bootstrap/dist/css/bootstrap.min.css'
import React from 'react'
import { connect } from 'react-redux'
import LoadingBar from 'react-redux-loading-bar'
import Navigation from './navigation'
import Footer from './footer'

const App = ({ title, component, payload }) => {
const Component = require(`../containers/${component}`).default

return (
<div>
<Navigation />
<LoadingBar />
<Component {...payload} />
<Footer />
</div>
)
}

export default connect(
(state, props) => ({
...props,
...state.app
})
)(App)

+ 17
- 0
src/components/commit.js View File

@@ -0,0 +1,17 @@
import React from 'react'
import { Alert } from 'reactstrap'
import TimeAgo from 'react-timeago'

const Commit = ({ url, message, author, date }) => url ? (
<Alert className='text-center' color='info'>
<b>Latest commit:</b>{' '}
<a href={url}>{message}</a> by{' '}
<a href={author.url}>
<img src={author.avatar} width='30' height='30' alt='Avatar' className='rounded' />{' '}
{author.name}
</a>{' '}
<span className='text-muted'><TimeAgo date={date} /></span>
</Alert>
) : (<noscript />)

export default Commit

+ 17
- 0
src/components/feature.js View File

@@ -0,0 +1,17 @@
import React from 'react'
import { Col, Card, CardImg, CardText, CardBody, CardTitle, CardSubtitle } from 'reactstrap'

const Feature = ({ image, title, subtitle, description }) => (
<Col md={4} style={{ marginBottom: 15 }}>
<Card>
<CardImg left src={image} />
<CardBody>
<CardTitle>{title}</CardTitle>
<CardSubtitle>{subtitle}</CardSubtitle>
<CardText>{description}</CardText>
</CardBody>
</Card>
</Col>
)

export default Feature

+ 17
- 0
src/components/footer.js View File

@@ -0,0 +1,17 @@
import React from 'react'
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
import { faCoffee, faHeart } from '@fortawesome/fontawesome-free-solid'
import Layout from './layout'

const Footer = () => (
<div>
<hr />
<Layout>
Developed with <FontAwesomeIcon icon={faHeart} /> and <FontAwesomeIcon icon={faCoffee} /> using{' '}
<a href='https://getbootstrap.com/'>Bootstrap</a>, <a href='https://reactjs.org/'>React</a> and{' '}
<a href='https://fontawesome.com/'>Font Awesome</a>
</Layout>
</div>
)

export default Footer

+ 41
- 0
src/components/hero.js View File

@@ -0,0 +1,41 @@
import React from 'react'
import { Button, Jumbotron } from 'reactstrap'
import FontAwesomeIcon from '@fortawesome/react-fontawesome'

const Hero = ({ title, logo, description, buttons, release }) => (
<Jumbotron fluid style={{
background: 'linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url(/img/fullpic.png)',
backgroundPosition: 'center',
backgroundSize: 'cover',
color: 'white',
textShadow: '1px 1px 2px black',
display: 'table',
width: '100%',
margin: 0
}}>
<div style={{
display: 'table-cell',
verticalAlign: 'middle',
textAlign: 'center'
}}>
<div style={{ maxWidth: 900, margin: 'auto' }}>
<img src={logo} alt='Logo' />
<h1 className='display-1'>{title}</h1>
<p className='lead'>{description}</p>
<p className='lead'>
{buttons.map(({ link, color, icon, text }) => (
<span>
<Button color={color} href={link}>
<FontAwesomeIcon icon={icon} /> {text}
</Button>
{' '}
</span>
))}
</p>
<p className='lead'>Latest release: <b>{release || 'unknown'}</b></p>
</div>
</div>
</Jumbotron>
)

export default Hero

+ 10
- 0
src/components/layout.js View File

@@ -0,0 +1,10 @@
import React from 'react'
import { Container } from 'reactstrap'

const Layout = ({ children }) => (
<Container style={{ marginTop: 15, marginBottom: 15 }}>
{children}
</Container>
)

export default Layout

+ 52
- 0
src/components/navigation.js View File

@@ -0,0 +1,52 @@
import React from 'react'
import { NavLink as ActiveLink } from 'redux-first-router-link'
import { Collapse, Navbar, NavbarToggler, NavbarBrand, Nav, NavItem, NavLink } from 'reactstrap'
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
import { faCogs, faNewspaper, faFileAlt } from '@fortawesome/fontawesome-free-solid'
import { faGithub, faDiscord } from '@fortawesome/fontawesome-free-brands'
import hero from '../_data/hero'

class Navigation extends React.Component {
constructor (props) {
super(props)

this.toggle = this.toggle.bind(this)
this.state = { isOpen: false }
}

toggle () {
this.setState({ isOpen: !this.state.isOpen })
}

render () {
return (
<Navbar color='faded' light expand='md'>
<NavbarBrand tag={ActiveLink} to='/'><img src={hero.logo} alt='Logo' width='30' height='30' /> Home</NavbarBrand>
<NavbarToggler onClick={this.toggle} />
<Collapse isOpen={this.state.isOpen} navbar>
<Nav navbar>
<NavItem>
<NavLink tag={ActiveLink} to='/features'><FontAwesomeIcon icon={faCogs} /> Features</NavLink>
</NavItem>
<NavItem>
<NavLink tag={ActiveLink} to='/blog'><FontAwesomeIcon icon={faNewspaper} /> Blog</NavLink>
</NavItem>
<NavItem>
<NavLink href='https://github.com/runelite/runelite/wiki'><FontAwesomeIcon icon={faFileAlt} /> Wiki</NavLink>
</NavItem>
</Nav>
<Nav navbar className='ml-auto'>
<NavItem>
<NavLink href='https://discord.gg/mePCs8U'><FontAwesomeIcon icon={faDiscord} /> Discord</NavLink>
</NavItem>
<NavItem>
<NavLink href='https://github.com/runelite'><FontAwesomeIcon icon={faGithub} /> GitHub</NavLink>
</NavItem>
</Nav>
</Collapse>
</Navbar>
)
}
}

export default Navigation

+ 39
- 0
src/containers/404.js View File

@@ -0,0 +1,39 @@
import React from 'react'
import { Helmet } from 'react-helmet'
import { NavLink } from 'redux-first-router-link'
import hero from '../_data/hero'

const Error404 = (store) => (
<div style={{
backgroundImage: 'url(/img/cat.jpg)',
display: 'table',
width: '100%',
height: 'calc(100% - 3.5em)',
boxShadow: 'inset 0 0 5rem rgba(0,0,0,.5)'
}}>
<Helmet>
<title>404 - {hero.title}</title>
</Helmet>
<div style={{
display: 'table-cell',
verticalAlign: 'middle'
}}>
<div style={{
marginLeft: 'auto',
marginRight: 'auto',
textAlign: 'center',
fontWeight: 700
}}>
<h1>
404
</h1>
<p className='lead'>
Sorry, page was not found
Click <NavLink to='/'>here</NavLink> to return to home page.
</p>
</div>
</div>
</div>
)

export default Error404

+ 26
- 0
src/containers/blog-show.js View File

@@ -0,0 +1,26 @@
import 'github-markdown-css'
import React from 'react'
import { Helmet } from 'react-helmet'
import TimeAgo from 'react-timeago'
import Layout from '../components/layout'
import blog from '../blog'
import hero from '../_data/hero'

const BlogShow = ({ slug }) => {
const post = blog.get(slug)
const { date, title, description, __content } = post

return (
<Layout>
<Helmet>
<title>{title} - {hero.title}</title>
<meta name='description' content={description} />
</Helmet>
<h1>{title} <small className='text-muted'><TimeAgo date={date} /></small></h1>
<hr />
<div className='markdown-body' dangerouslySetInnerHTML={{__html: __content}} />
</Layout>
)
}

export default BlogShow

+ 34
- 0
src/containers/blog.js View File

@@ -0,0 +1,34 @@
import 'github-markdown-css'
import React from 'react'
import { Helmet } from 'react-helmet'
import TimeAgo from 'react-timeago'
import { ListGroup, ListGroupItem, ListGroupItemHeading, ListGroupItemText } from 'reactstrap'
import { NavLink } from 'redux-first-router-link'
import Layout from '../components/layout'
import blog from '../blog'
import hero from '../_data/hero'

const Blog = ({ slug }) => (
<Layout>
<Helmet>
<title>Blog - {hero.title}</title>
</Helmet>
<h1>Blog</h1>
<hr />
<ListGroup>
{[...blog.keys()].map(path => {
const { date, title, description } = blog.get(path)
return (
<ListGroupItem tag={NavLink} to={`/blog/show/${path}`}>
<ListGroupItemHeading>{title || path} <small className='text-muted'><TimeAgo date={date} /></small></ListGroupItemHeading>
<ListGroupItemText className='text-muted'>
{description}
</ListGroupItemText>
</ListGroupItem>
)
})}
</ListGroup>
</Layout>
)

export default Blog

+ 22
- 0
src/containers/features.js View File

@@ -0,0 +1,22 @@
import React from 'react'
import { Helmet } from 'react-helmet'
import { Row } from 'reactstrap'
import Layout from '../components/layout'
import Feature from '../components/feature'
import features from '../_data/features'
import hero from '../_data/hero'

const Features = () => (
<Layout>
<Helmet>
<title>Features - {hero.title}</title>
</Helmet>
<h1>Features</h1>
<hr />
<Row>
{features.map(feature => (<Feature {...feature} />))}
</Row>
</Layout>
)

export default Features

+ 45
- 0
src/containers/home.js View File

@@ -0,0 +1,45 @@
import 'github-markdown-css'
import React from 'react'
import { connect } from 'react-redux'
import { Helmet } from 'react-helmet'
import { Row } from 'reactstrap'
import { NavLink } from 'redux-first-router-link'
import Feature from '../components/feature'
import Commit from '../components/commit'
import Layout from '../components/layout'
import Hero from '../components/hero'
import { getLatest } from '../blog'
import { latestCommitSelector, latestReleaseSelector } from '../redux/modules/git'
import hero from '../_data/hero'
import features from '../_data/features'

const Home = ({ commit, release }) => (
<div>
<Helmet>
<title>{hero.title} - Open Source Old School RuneScape Client</title>
<meta name='description' content={hero.description} />
</Helmet>
<Hero {...hero} release={release.name} />
<Layout>
<Commit {...commit} />
<h1>Features <NavLink to='/features' style={{ fontSize: 18 }}>See all features...</NavLink></h1>
<hr />
<Row>
{features
.filter(feature => feature.home)
.map(({ image, title }) => ({ image, title }))
.map(feature => (<Feature {...feature} />))}
</Row>
<h1>Latest news <NavLink to='/blog' style={{ fontSize: 18 }}>See all news...</NavLink></h1>
<hr />
<div className='markdown-body' dangerouslySetInnerHTML={{__html: getLatest().__content}} />
</Layout>
</div>
)

export default connect(
(state, props) => ({
commit: latestCommitSelector(state, props),
release: latestReleaseSelector(state, props)
})
)(Home)

+ 21
- 0
src/containers/logged-in.js View File

@@ -0,0 +1,21 @@
import React from 'react'
import { Helmet } from 'react-helmet'
import Layout from '../components/layout'
import hero from '../_data/hero'

const LoggedIn = ({ commit }) => (
<div>
<Helmet>
<title>Succesfully logged in - {hero.title}</title>
</Helmet>
<Layout>
<h1>Congratulations!</h1>
<hr />
<div>
You succesfully logged into the RuneLite. You can now close this window.
</div>
</Layout>
</div>
)

export default LoggedIn

+ 22
- 0
src/index.js View File

@@ -0,0 +1,22 @@
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import createHistory from 'history/createBrowserHistory'
import App from './components/app'
import configureStore from './redux/store'
import registerServiceWorker from './service-worker'

// Create a history of your choosing (we're using a browser history in this case)
const history = createHistory()

// Create redux store
const store = configureStore(history)

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

registerServiceWorker()

+ 15
- 0
src/redux/middleware/thunkMiddleware.js View File

@@ -0,0 +1,15 @@
function createThunkMiddleware () {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState)
}

if (action && typeof action.payload === 'function') {
return action.payload(dispatch, getState)
}

return next(action)
}
}

export default createThunkMiddleware()

+ 38
- 0
src/redux/modules/app.js View File

@@ -0,0 +1,38 @@
import { handleActions } from 'redux-actions'
import { NOT_FOUND } from 'redux-first-router'

// Reducer
export default handleActions({
[NOT_FOUND]: (state, { payload }) => ({
...state,
component: '404',
payload: payload
}),
HOME: (state, { payload }) => ({
...state,
component: 'home',
payload: payload
}),
BLOG: (state, { payload }) => ({
...state,
component: 'blog',
payload: payload
}),
FEATURES: (state, { payload }) => ({
...state,
component: 'features',
payload: payload
}),
BLOG_SHOW: (state, { payload }) => ({
...state,
component: 'blog-show',
payload: payload
}),
LOGGED_IN: (state, { payload }) => ({
...state,
component: 'logged-in',
payload: payload
})
}, {
component: 'home'
})

+ 103
- 0
src/redux/modules/git.js View File

@@ -0,0 +1,103 @@
import { createAction, combineActions, handleActions } from 'redux-actions'
import { createRoutine } from 'redux-routines'
import { createSelector } from 'reselect'
import api from '../../api'
import git from '../../_data/git'

const githubApi = api('https://api.github.com/')

// Actions
export const getCommitsRoutine = createRoutine('react-ui/git/GET_COMMITS')
export const getReleasesRoutine = createRoutine('react-ui/git/GET_RELEASES')

// Reducer
export default handleActions({
[combineActions(getCommitsRoutine.SUCCESS)]: (state, { payload }) => ({
...state,
commits: payload
}),
[combineActions(getReleasesRoutine.SUCCESS)]: (state, { payload }) => ({
...state,
releases: payload
})
}, {
commits: [],
releases: []
})

// Action creators
export const getCommits = createAction(getCommitsRoutine.TRIGGER, (payload) => async (dispatch) => {
try {
dispatch(getCommitsRoutine.request())
const response = await githubApi.wrapFailure(dispatch, githubApi.fetch(
`repos/${git.user}/${git.repository}/commits`, { method: 'GET' }
))

dispatch(getCommitsRoutine.success(response))
return response
} catch (e) {
dispatch(getCommitsRoutine.failure(e))
} finally {
dispatch(getCommitsRoutine.fulfill())
}
})

export const getReleases = createAction(getCommitsRoutine.TRIGGER, (payload) => async (dispatch) => {
try {
dispatch(getReleasesRoutine.request())
const response = await githubApi.wrapFailure(dispatch, githubApi.fetch(
`repos/${git.user}/${git.repository}/tags`, { method: 'GET' }
))

dispatch(getReleasesRoutine.success(response))
return response
} catch (e) {
dispatch(getReleasesRoutine.failure(e))
} finally {
dispatch(getReleasesRoutine.fulfill())
}
})

// Selectors
const commitsSelector = state => state.git.commits
const releasesSelector = state => state.git.releases

export const latestCommitSelector = createSelector(
commitsSelector,
commits => {
const realCommits = commits.filter(commit => commit.parents.length <= 1)

if (realCommits.length > 0) {
const commit = realCommits[0]
return {
url: commit.html_url,
message: commit.commit.message.substr(0, 50),
date: commit.commit.committer.date,
author: {
name: commit.commit.author.name,
url: commit.author.html_url,
avatar: commit.author.avatar_url
}
}
}

return {}
}
)

export const latestReleaseSelector = createSelector(
releasesSelector,
releases => {
console.log(releases)
if (releases.length > 0) {
const release = releases[0]
return {
name: release.name.substr(
release.name.lastIndexOf('-') + 1,
release.name.length)
}
}

return {}
}
)

+ 8
- 0
src/redux/reducer.js View File

@@ -0,0 +1,8 @@
import appReducer from './modules/app'
import gitReducer from './modules/git'

// Combine all redux reducers into one root reducer
export default {
app: appReducer,
git: gitReducer
}

+ 20
- 0
src/redux/routes.js View File

@@ -0,0 +1,20 @@
import R from 'ramda'
import { getCommits, getReleases } from './modules/git'

const createThunk = (fns) => async (dispatch, getState) => R.reduce(
(a, b) => a.then((r) => dispatch(b(getState().location.payload.slug))),
Promise.resolve(),
fns)

const createRoute = (path, fns) => ({
path,
thunk: createThunk(fns || [])
})

export default {
HOME: createRoute('/', [ getCommits, getReleases ]),
BLOG: createRoute('/blog'),
FEATURES: createRoute('/features'),
BLOG_SHOW: createRoute('/blog/show/:slug'),
LOGGED_IN: createRoute('/logged-in')
}

+ 56
- 0
src/redux/store.js View File

@@ -0,0 +1,56 @@
import { applyMiddleware, createStore, combineReducers } from 'redux'
import { connectRoutes } from 'redux-first-router'
import { composeWithDevTools } from 'remote-redux-devtools'
import { loadingBarReducer, loadingBarMiddleware } from 'react-redux-loading-bar'
import thunkMiddleware from './middleware/thunkMiddleware'
import rootReducer from './reducer'
import routes from './routes'

/**
* Configure react store
*/
const configureStore = (history, initialState) => {
// Create router enhancers
const { reducer, middleware, enhancer } = connectRoutes(
history,
routes
)

// Combine all reducers
const reducers = combineReducers({
...rootReducer,
location: reducer,
loadingBar: loadingBarReducer
})

// Apply all middlewares
const middlewares = applyMiddleware(
loadingBarMiddleware({
promiseTypeSuffixes: ['REQUEST', 'FULFILL', 'FULFILL']
}),
thunkMiddleware,
middleware
)

// Compose our enhancer from various middlewares
const enhancers = composeWithDevTools(enhancer, middlewares)

// Create our store from rootReducer and initial state
const store = createStore(reducers, initialState, enhancers)

if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('./reducer', () => {
const nextRootReducer = require('./reducer').default
store.replaceReducer(combineReducers({
...nextRootReducer,
location: reducer
})
)
})
}

return store
}

export default configureStore

+ 108
- 0
src/service-worker.js View File

@@ -0,0 +1,108 @@
// In production, we register a service worker to serve assets from local cache.

// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.

// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.

const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
)

export default function register () {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new window.URL(process.env.PUBLIC_URL, window.location)
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets see https://github.com/facebookincubator/create-react-app/issues/2374
return
}

window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`

if (!isLocalhost) {
// Is not local host. Just register service worker
registerValidSW(swUrl)
} else {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl)
}
})
}
}

function registerValidSW (swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available please refresh." message in your web app.
console.log('New content is available please refresh.')
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.')
}
}
}
}
})
.catch(error => {
console.error('Error during service worker registration:', error)
})
}

function checkValidServiceWorker (swUrl) {
// Check if the service worker can be found. If it can't reload the page.
window.fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl)
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
)
})
}

export function unregister () {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister()
})
}
}

Loading…
Cancel
Save