reorg
This commit is contained in:
parent
bddd68b723
commit
c3b9f40af8
|
@ -1,13 +1,13 @@
|
|||
import React from 'react';
|
||||
import { RouterMixin, navigate } from 'react-mini-router';
|
||||
import _ from 'lodash';
|
||||
import lodash from './mixins/lodash.js';
|
||||
import './modules/lodash.js';
|
||||
|
||||
import ProjectsPage from './pages/ProjectsPage.jsx';
|
||||
import MilestonesPage from './pages/MilestonesPage.jsx';
|
||||
import ChartPage from './pages/ChartPage.jsx';
|
||||
import AddProjectPage from './pages/AddProjectPage.jsx';
|
||||
import NotFoundPage from './pages/NotFoundPage.jsx';
|
||||
import ProjectsPage from './components/pages/ProjectsPage.jsx';
|
||||
import MilestonesPage from './components/pages/MilestonesPage.jsx';
|
||||
import ChartPage from './components/pages/ChartPage.jsx';
|
||||
import AddProjectPage from './components/pages/AddProjectPage.jsx';
|
||||
import NotFoundPage from './components/pages/NotFoundPage.jsx';
|
||||
|
||||
import actions from './actions/appActions.js';
|
||||
|
||||
|
@ -33,7 +33,7 @@ let find = ({ to, params, query }) => {
|
|||
let re = /:[^\/]+/g;
|
||||
|
||||
// Skip empty objects.
|
||||
[ params, query ] = [_.isObject(params) ? params : {}, query ].map(o => _.pick(o, _.identity));
|
||||
[ params, query ] = [ _.isObject(params) ? params : {}, query ].map(o => _.pick(o, _.identity));
|
||||
|
||||
// Find among the routes.
|
||||
_.find(routes, (name, url) => {
|
||||
|
@ -73,9 +73,7 @@ export default React.createClass({
|
|||
|
||||
statics: {
|
||||
// Build a link to a page.
|
||||
link: (route) => {
|
||||
return find(route);
|
||||
},
|
||||
link: (route) => find(route),
|
||||
// Route to a link.
|
||||
navigate: (route) => {
|
||||
let fn = _.isString(route) ? _.identity : find;
|
||||
|
@ -86,25 +84,21 @@ export default React.createClass({
|
|||
// Show projects.
|
||||
projects() {
|
||||
document.title = 'Burnchart: GitHub Burndown Chart as a Service';
|
||||
process.nextTick(() => { actions.emit('projects.load'); });
|
||||
process.nextTick(() => actions.emit('projects.load'));
|
||||
return <ProjectsPage />;
|
||||
},
|
||||
|
||||
// Show project milestones.
|
||||
milestones(owner, name) {
|
||||
document.title = `${owner}/${name}`;
|
||||
process.nextTick(() => {
|
||||
actions.emit('projects.load', { owner, name });
|
||||
});
|
||||
process.nextTick(() => actions.emit('projects.load', { owner, name }));
|
||||
return <MilestonesPage owner={owner} name={name} />;
|
||||
},
|
||||
|
||||
// Show a project milestone chart.
|
||||
chart(owner, name, milestone) {
|
||||
document.title = `${owner}/${name}/${milestone}`;
|
||||
process.nextTick(() => {
|
||||
actions.emit('projects.load', { owner, name, milestone });
|
||||
});
|
||||
process.nextTick(() => actions.emit('projects.load', { owner, name, milestone }));
|
||||
return <ChartPage owner={owner} name={name} milestone={milestone} />;
|
||||
},
|
||||
|
||||
|
@ -134,6 +128,8 @@ export default React.createClass({
|
|||
return <div />;
|
||||
} else {
|
||||
blank = true;
|
||||
// Clear any notifications.
|
||||
process.nextTick(() => actions.emit('system.notify'));
|
||||
return this.renderCurrentRoute();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import EventEmitter from '../core/EventEmitter.js';
|
||||
import EventEmitter from '../lib/EventEmitter.js';
|
||||
|
||||
// Just a namespace for all actions.
|
||||
export default new EventEmitter();
|
||||
|
|
|
@ -20,12 +20,12 @@ export default React.createClass({
|
|||
this.setState({ 'val': evt.target.value });
|
||||
},
|
||||
|
||||
// Add the project (via Enter keypress).
|
||||
_onKeyUp(evt) {
|
||||
if (evt.key == 'Enter') {
|
||||
this._onAdd();
|
||||
}
|
||||
if (evt.key == 'Enter') this._onAdd();
|
||||
},
|
||||
|
||||
// Add the project.
|
||||
_onAdd() {
|
||||
let [ owner, name ] = this.state.val.split('/');
|
||||
actions.emit('projects.add', { owner, name });
|
||||
|
@ -33,6 +33,7 @@ export default React.createClass({
|
|||
App.navigate({ 'to': 'projects' });
|
||||
},
|
||||
|
||||
// Blank input.
|
||||
getInitialState() {
|
||||
return { 'val': '' };
|
||||
},
|
||||
|
@ -76,6 +77,7 @@ export default React.createClass({
|
|||
);
|
||||
},
|
||||
|
||||
// Focus input field on mount.
|
||||
componentDidMount() {
|
||||
this.refs.el.focus();
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import d3Tip from 'd3-tip';
|
||||
|
||||
d3Tip(d3);
|
||||
|
||||
import Format from '../mixins/Format.js';
|
||||
import format from '../modules/format.js';
|
||||
|
||||
import lines from '../modules/chart/lines.js';
|
||||
import axes from '../modules/chart/axes.js';
|
||||
|
@ -13,8 +12,6 @@ export default React.createClass({
|
|||
|
||||
displayName: 'Chart.jsx',
|
||||
|
||||
mixins: [ Format ],
|
||||
|
||||
render() {
|
||||
let milestone = this.props.milestone;
|
||||
|
||||
|
@ -22,9 +19,9 @@ export default React.createClass({
|
|||
<div>
|
||||
<div id="title">
|
||||
<div className="wrap">
|
||||
<h2 className="title">{this._title(milestone.title)}</h2>
|
||||
<span className="sub">{this._due(milestone.due_on)}</span>
|
||||
<div className="description">{this._markdown(milestone.description)}</div>
|
||||
<h2 className="title">{format.title(milestone.title)}</h2>
|
||||
<span className="sub">{format.due(milestone.due_on)}</span>
|
||||
<div className="description">{format.markdown(milestone.description)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="content" className="wrap">
|
||||
|
@ -35,8 +32,8 @@ export default React.createClass({
|
|||
},
|
||||
|
||||
componentDidMount() {
|
||||
let milestone = this.props.milestone,
|
||||
issues = milestone.issues;
|
||||
let milestone = this.props.milestone;
|
||||
let issues = milestone.issues;
|
||||
// Total number of points in the milestone.
|
||||
let total = issues.open.size + issues.closed.size;
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
|
||||
export default React.createClass({
|
||||
|
||||
displayName: 'Footer.jsx',
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="footer">
|
||||
<div className="wrap">
|
||||
© 2012-2016 <a href="https:/radekstepan.com" target="_blank">Radek Stepan</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
|
@ -20,6 +20,7 @@ export default React.createClass({
|
|||
actions.emit('user.signout');
|
||||
},
|
||||
|
||||
// Add example projects.
|
||||
_onDemo() {
|
||||
actions.emit('projects.demo');
|
||||
},
|
||||
|
@ -28,12 +29,9 @@ export default React.createClass({
|
|||
// From app store.
|
||||
let props = this.props.app;
|
||||
|
||||
// Switch loading icon with app icon.
|
||||
let icon = [ 'fire', 'spinner' ][ +props.system.loading ];
|
||||
|
||||
// Sign-in/out.
|
||||
let user;
|
||||
if (props.user && 'uid' in props.user) {
|
||||
if (props.user != null && 'uid' in props.user) {
|
||||
user = (
|
||||
<div className="right">
|
||||
<a onClick={this._onSignOut}>
|
||||
|
@ -51,24 +49,27 @@ export default React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
// Switch loading icon with app icon.
|
||||
let icon = [ 'fire', 'spinner' ][ +props.system.loading ];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Notify {...props.system.notification} />
|
||||
<div id="head">
|
||||
{user}
|
||||
|
||||
<Link route={{ to: 'projects' }} id="icon">
|
||||
<Link route={{ 'to': 'projects' }} id="icon">
|
||||
<Icon name={icon} />
|
||||
</Link>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<Link route={{ to: 'addProject' }}>
|
||||
<Link route={{ 'to': 'addProject' }}>
|
||||
<Icon name="plus" /> Add a Project
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link route={{ to: 'demo' }}>
|
||||
<Link route={{ 'to': 'demo' }}>
|
||||
<Icon name="computer" /> See Examples
|
||||
</Link>
|
||||
</li>
|
||||
|
|
|
@ -9,6 +9,7 @@ export default React.createClass({
|
|||
|
||||
displayName: 'Hero.jsx',
|
||||
|
||||
// Add example projects.
|
||||
_onDemo() {
|
||||
actions.emit('projects.demo');
|
||||
},
|
||||
|
@ -19,10 +20,15 @@ export default React.createClass({
|
|||
<div className="content">
|
||||
<Icon name="direction" />
|
||||
<h2>See your project progress</h2>
|
||||
<p>Serious about a project deadline? Add your GitHub project and we'll tell you if it is running on time or not.</p>
|
||||
<p>Serious about a project deadline? Add your GitHub project
|
||||
and we'll tell you if it is running on time or not.</p>
|
||||
<div className="cta">
|
||||
<Link route={{ to: 'addProject' }} className="primary"><Icon name="plus" /> Add a Project</Link>
|
||||
<Link route={{ to: 'demo' }} className="secondary"><Icon name="computer" /> See Examples</Link>
|
||||
<Link route={{ to: 'addProject' }} className="primary">
|
||||
<Icon name="plus" /> Add a Project
|
||||
</Link>
|
||||
<Link route={{ to: 'demo' }} className="secondary">
|
||||
<Icon name="computer" /> See Examples
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import Format from '../mixins/Format.js';
|
||||
import format from '../modules/format.js';
|
||||
|
||||
// Fontello icon hex codes.
|
||||
let codes = {
|
||||
|
@ -25,17 +25,15 @@ export default React.createClass({
|
|||
|
||||
displayName: 'Icon.jsx',
|
||||
|
||||
mixins: [ Format ],
|
||||
|
||||
render() {
|
||||
let name = this.props.name;
|
||||
|
||||
if (name && name in codes) {
|
||||
let code = this._hexToDec(codes[name]);
|
||||
let code = format.hexToDec(codes[name]);
|
||||
return (
|
||||
<span
|
||||
className={'icon ' + name}
|
||||
dangerouslySetInnerHTML={{ '__html': '&#' + code + ';' }}
|
||||
className={`icon ${name}`}
|
||||
dangerouslySetInnerHTML={{ '__html': `&#${code};` }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ export default React.createClass({
|
|||
|
||||
displayName: 'Link.jsx',
|
||||
|
||||
// Navigate to a route.
|
||||
_navigate(link, evt) {
|
||||
App.navigate(link);
|
||||
evt.preventDefault();
|
||||
|
@ -18,7 +19,7 @@ export default React.createClass({
|
|||
return (
|
||||
<a
|
||||
{...this.props}
|
||||
href={'#!' + link}
|
||||
href={`#!${link}`}
|
||||
onClick={this._navigate.bind(this, link)}
|
||||
>
|
||||
{this.props.children}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import _ from 'lodash';
|
||||
import cls from 'classnames';
|
||||
|
||||
import Format from '../mixins/Format.js';
|
||||
import format from '../modules/format.js';
|
||||
|
||||
import actions from '../actions/appActions.js';
|
||||
|
||||
|
@ -13,8 +13,7 @@ export default React.createClass({
|
|||
|
||||
displayName: 'Milestones.jsx',
|
||||
|
||||
mixins: [ Format ],
|
||||
|
||||
// Cycle through milestones sort order.
|
||||
_onSort() {
|
||||
actions.emit('projects.sort');
|
||||
},
|
||||
|
@ -36,7 +35,7 @@ export default React.createClass({
|
|||
);
|
||||
}).value();
|
||||
|
||||
// Now for the list of milestones.
|
||||
// Now for the list of milestones, index sorted.
|
||||
let list = [];
|
||||
_.each(projects.index, ([ pI, mI ]) => {
|
||||
let { owner, name, milestones } = projects.list[pI];
|
||||
|
@ -48,12 +47,18 @@ export default React.createClass({
|
|||
list.push(
|
||||
<tr className={cls({ 'done': milestone.stats.isDone })} key={`${pI}-${mI}`}>
|
||||
<td className="repo">
|
||||
<Link route={{ 'to': 'milestones', 'params': { owner, name } }} className="project">
|
||||
<Link
|
||||
route={{ 'to': 'milestones', 'params': { owner, name } }}
|
||||
className="project"
|
||||
>
|
||||
{owner}/{name}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<Link route={{ 'to': 'chart', 'params': { owner, name, 'milestone': milestone.number } }} className="milestone">
|
||||
<Link
|
||||
route={{ 'to': 'chart', 'params': { owner, name, 'milestone': milestone.number } }}
|
||||
className="milestone"
|
||||
>
|
||||
{milestone.title}
|
||||
</Link>
|
||||
</td>
|
||||
|
@ -61,7 +66,7 @@ export default React.createClass({
|
|||
<div className="progress">
|
||||
<span className="percent">{Math.floor(milestone.stats.progress.points)}%</span>
|
||||
<span className={cls('due', { 'red': milestone.stats.isOverdue })}>
|
||||
{this._due(milestone.due_on)}
|
||||
{format.due(milestone.due_on)}
|
||||
</span>
|
||||
<div className="outer bar">
|
||||
<div
|
||||
|
@ -79,6 +84,7 @@ export default React.createClass({
|
|||
if (!errors.length && !list.length) return false;
|
||||
|
||||
if (project) {
|
||||
// List of projects and their milestones.
|
||||
return (
|
||||
<div id="projects">
|
||||
<div className="header">
|
||||
|
@ -86,14 +92,13 @@ export default React.createClass({
|
|||
<h2>Milestones</h2>
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
{list}
|
||||
</tbody>
|
||||
<tbody>{list}</tbody>
|
||||
</table>
|
||||
<div className="footer" />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// Project-specific milestones.
|
||||
return (
|
||||
<div id="projects">
|
||||
<div className="header">
|
||||
|
|
|
@ -9,15 +9,20 @@ let Notify = React.createClass({
|
|||
|
||||
displayName: 'Notify.jsx',
|
||||
|
||||
// Close notification.
|
||||
_onClose() {
|
||||
actions.emit('system.notify');
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
// No text.
|
||||
'text': null,
|
||||
// Grey style.
|
||||
'type': '',
|
||||
// Top notification.
|
||||
'system': false,
|
||||
// Just announcing.
|
||||
'icon': 'megaphone'
|
||||
};
|
||||
},
|
||||
|
@ -25,6 +30,7 @@ let Notify = React.createClass({
|
|||
render() {
|
||||
let { text, system, type, icon, ttl } = this.props;
|
||||
|
||||
// No text = no message.
|
||||
if (!text) return false;
|
||||
|
||||
if (system) {
|
||||
|
@ -53,6 +59,7 @@ export default React.createClass({
|
|||
render() {
|
||||
if (!this.props.id) return false; // TODO: fix ghost
|
||||
|
||||
// Top bar or center positioned?
|
||||
let name = (this.props.system) ? 'animCenter' : 'animTop';
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
// Inserts a space before rendering text.
|
||||
export default React.createClass({
|
||||
|
||||
displayName: 'Space.jsx',
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
|
||||
import actions from '../../actions/appActions.js';
|
||||
|
||||
import Page from '../../lib/PageMixin.js';
|
||||
|
||||
import Notify from '../Notify.jsx';
|
||||
import Header from '../Header.jsx';
|
||||
import Footer from '../Footer.jsx';
|
||||
import AddProjectForm from '../AddProjectForm.jsx';
|
||||
|
||||
export default React.createClass({
|
||||
|
||||
displayName: 'AddProjectPage.jsx',
|
||||
|
||||
mixins: [ Page ],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Notify />
|
||||
<Header {...this.state} />
|
||||
|
||||
<div id="page">
|
||||
<div id="content" className="wrap">
|
||||
<AddProjectForm user={this.state.app.user} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
|
@ -1,11 +1,12 @@
|
|||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Page from '../mixins/Page.js';
|
||||
import Page from '../../lib/PageMixin.js';
|
||||
|
||||
import Notify from '../components/Notify.jsx';
|
||||
import Header from '../components/Header.jsx';
|
||||
import Chart from '../components/Chart.jsx';
|
||||
import Notify from '../Notify.jsx';
|
||||
import Header from '../Header.jsx';
|
||||
import Footer from '../Footer.jsx';
|
||||
import Chart from '../Chart.jsx';
|
||||
|
||||
export default React.createClass({
|
||||
|
||||
|
@ -32,9 +33,7 @@ export default React.createClass({
|
|||
return false;
|
||||
});
|
||||
|
||||
if (milestone) {
|
||||
content = <Chart milestone={milestone} />;
|
||||
}
|
||||
if (milestone) content = <Chart milestone={milestone} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -44,11 +43,7 @@ export default React.createClass({
|
|||
|
||||
<div id="page">{content}</div>
|
||||
|
||||
<div id="footer">
|
||||
<div className="wrap">
|
||||
© 2012-2016 <a href="https:/radekstepan.com" target="_blank">Radek Stepan</a>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
import React from 'react';
|
||||
|
||||
import Page from '../mixins/Page.js';
|
||||
import Page from '../../lib/PageMixin.js';
|
||||
|
||||
import Notify from '../components/Notify.jsx';
|
||||
import Header from '../components/Header.jsx';
|
||||
import Milestones from '../components/Milestones.jsx';
|
||||
import Notify from '../Notify.jsx';
|
||||
import Header from '../Header.jsx';
|
||||
import Footer from '../Footer.jsx';
|
||||
import Milestones from '../Milestones.jsx';
|
||||
|
||||
export default React.createClass({
|
||||
|
||||
|
@ -33,11 +34,7 @@ export default React.createClass({
|
|||
<div id="content" className="wrap">{content}</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<div className="wrap">
|
||||
© 2012-2016 <a href="https:/radekstepan.com" target="_blank">Radek Stepan</a>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
|
||||
import Page from '../mixins/Page.js';
|
||||
import Page from '../../lib/PageMixin.js';
|
||||
|
||||
// TODO: implement
|
||||
export default React.createClass({
|
||||
|
||||
displayName: 'NotFoundPage.jsx',
|
|
@ -1,11 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
import Page from '../mixins/Page.js';
|
||||
import Page from '../../lib/PageMixin.js';
|
||||
|
||||
import Notify from '../components/Notify.jsx';
|
||||
import Header from '../components/Header.jsx';
|
||||
import Milestones from '../components/Milestones.jsx';
|
||||
import Hero from '../components/Hero.jsx';
|
||||
import Notify from '../Notify.jsx';
|
||||
import Header from '../Header.jsx';
|
||||
import Footer from '../Footer.jsx';
|
||||
import Milestones from '../Milestones.jsx';
|
||||
import Hero from '../Hero.jsx';
|
||||
|
||||
export default React.createClass({
|
||||
|
||||
|
@ -33,11 +34,7 @@ export default React.createClass({
|
|||
<div id="content" className="wrap">{content}</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<div className="wrap">
|
||||
© 2012-2016 <a href="https:/radekstepan.com" target="_blank">Radek Stepan</a>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -11,7 +11,7 @@ export default class EventEmitter {
|
|||
emit(event, obj, ctx) {
|
||||
if (!event.length) return;
|
||||
|
||||
this.list.forEach(sub => {
|
||||
this.list.forEach((sub) => {
|
||||
if (sub.pattern.test(event)) {
|
||||
sub.cb.call(ctx, obj, event);
|
||||
}
|
||||
|
@ -31,12 +31,12 @@ export default class EventEmitter {
|
|||
|
||||
// Assume we can have multiple.
|
||||
off(path, cb) {
|
||||
_.remove(this.list, sub => sub.pattern.test(path) && sub.cb === cb);
|
||||
_.remove(this.list, (sub) => sub.pattern.test(path) && sub.cb === cb);
|
||||
}
|
||||
|
||||
// Remove all listeners with this callback.
|
||||
offAny(cb) {
|
||||
_.remove(this.list, sub => sub.cb === cb);
|
||||
_.remove(this.list, (sub) => sub.cb === cb);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
// TODO: the app store needs to go last because it loads user.
|
||||
import projectsStore from '../stores/projectsStore.js';
|
||||
import appStore from '../stores/appStore.js';
|
||||
|
||||
let stores = {
|
||||
'app': appStore,
|
||||
'projects': projectsStore
|
||||
};
|
||||
import stores from '../stores';
|
||||
|
||||
export default {
|
||||
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||
import d3 from 'd3';
|
||||
import moment from 'moment';
|
||||
|
||||
import config from '../../models/config.js';
|
||||
import config from '../../../config.js';
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
@ -6,36 +6,36 @@ export default {
|
|||
|
||||
// Time from now.
|
||||
// TODO: Memoize.
|
||||
_fromNow(jsonDate) {
|
||||
fromNow(jsonDate) {
|
||||
return moment(jsonDate, moment.ISO_8601).fromNow();
|
||||
},
|
||||
|
||||
// When is a milestone due?
|
||||
_due(jsonDate) {
|
||||
due(jsonDate) {
|
||||
if (!jsonDate) {
|
||||
return '\u00a0'; // for React
|
||||
} else {
|
||||
return [ 'due', this._fromNow(jsonDate) ].join(' ');
|
||||
return `due ${this.fromNow(jsonDate)}`;
|
||||
}
|
||||
},
|
||||
|
||||
// Markdown formatting.
|
||||
// TODO: works?
|
||||
_markdown(...args) {
|
||||
markdown(...args) {
|
||||
marked.apply(null, args);
|
||||
},
|
||||
|
||||
// Format milestone title.
|
||||
_title(text) {
|
||||
title(text) {
|
||||
if (text.toLowerCase().indexOf('milestone') > -1) {
|
||||
return text;
|
||||
} else {
|
||||
return [ 'Milestone', text ].join(' ');
|
||||
return `Milestone ${text}`;
|
||||
}
|
||||
},
|
||||
|
||||
// Hex to decimal.
|
||||
_hexToDec(hex) {
|
||||
hexToDec(hex) {
|
||||
return parseInt(hex, 16);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import _ from 'lodash';
|
||||
import async from 'async';
|
||||
|
||||
import config from '../../models/config.js';
|
||||
import config from '../../../config.js';
|
||||
import request from './request.js';
|
||||
|
||||
// Fetch issues for a milestone.
|
||||
|
|
|
@ -3,7 +3,7 @@ import superagent from 'superagent';
|
|||
|
||||
import actions from '../../actions/appActions.js';
|
||||
|
||||
import config from '../../models/config.js';
|
||||
import config from '../../../config.js';
|
||||
|
||||
// Custom JSON parser.
|
||||
superagent.parse = {
|
||||
|
@ -111,7 +111,7 @@ let request = ({ protocol, host, path, query, headers }, cb) => {
|
|||
|
||||
// How do we respond to a response?
|
||||
let response = (err, data, cb) => {
|
||||
if (err) return cb(error(err));
|
||||
if (err) return cb(error(data.body || err));
|
||||
// 2xx?
|
||||
if (data.statusType !== 2) return cb(error(data.body));
|
||||
// All good.
|
||||
|
@ -134,7 +134,7 @@ let headers = (token) => {
|
|||
// Parse an error.
|
||||
let error = (err) => {
|
||||
let text, type;
|
||||
|
||||
|
||||
switch (false) {
|
||||
case !_.isString(err):
|
||||
text = err;
|
||||
|
@ -155,13 +155,6 @@ let error = (err) => {
|
|||
text = err.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// API rate limit exceeded? Flash a message to that effect.
|
||||
// https://developer.github.com/v3/#rate-limiting
|
||||
if (/API rate limit exceeded/.test(text)) {
|
||||
type = 'warn';
|
||||
actions.emit('system.notify', { type, text });
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
|
|
@ -14,4 +14,4 @@ _.mixin({
|
|||
return obj;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import actions from '../actions/appActions.js';
|
||||
|
||||
import Page from '../mixins/Page.js';
|
||||
|
||||
import Notify from '../components/Notify.jsx';
|
||||
import Header from '../components/Header.jsx';
|
||||
import AddProjectForm from '../components/AddProjectForm.jsx';
|
||||
|
||||
export default React.createClass({
|
||||
|
||||
displayName: 'AddProjectPage.jsx',
|
||||
|
||||
mixins: [ Page ],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Notify />
|
||||
<Header {...this.state} />
|
||||
|
||||
<div id="page">
|
||||
<div id="content" className="wrap">
|
||||
<AddProjectForm user={this.state.app.user} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<div className="wrap">
|
||||
© 2012-2016 <a href="https:/radekstepan.com" target="_blank">Radek Stepan</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
|
@ -1,11 +1,11 @@
|
|||
import _ from 'lodash';
|
||||
import Firebase from 'firebase';
|
||||
|
||||
import Store from '../core/Store.js';
|
||||
import Store from '../lib/Store.js';
|
||||
|
||||
import actions from '../actions/appActions.js';
|
||||
|
||||
import config from '../models/config.js';
|
||||
import config from '../../config.js';
|
||||
|
||||
// Setup a new client.
|
||||
let client;
|
||||
|
@ -23,14 +23,12 @@ class AppStore extends Store {
|
|||
|
||||
// Listen to all app actions.
|
||||
actions.onAny((obj, event) => {
|
||||
let fn = ('on.' + event).replace(/[.]+(\w|$)/g, (m, p) => {
|
||||
return p.toUpperCase();
|
||||
});
|
||||
|
||||
let fn = `on.${event}`.replace(/[.]+(\w|$)/g, (m, p) => p.toUpperCase());
|
||||
// Run?
|
||||
(fn in this) && this[fn](obj);
|
||||
});
|
||||
|
||||
client = new Firebase("https://" + config.firebase + ".firebaseio.com");
|
||||
client = new Firebase(`https://${config.firebase}.firebaseio.com`);
|
||||
|
||||
// When user is already authenticated.
|
||||
client.onAuth((data={}) => actions.emit('user.ready', data));
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
// The app store needs to go last because it loads user.
|
||||
import projectsStore from './projectsStore.js';
|
||||
import appStore from './appStore.js';
|
||||
|
||||
export default {
|
||||
'app': appStore,
|
||||
'projects': projectsStore
|
||||
};
|
|
@ -4,7 +4,7 @@ import opa from 'object-path';
|
|||
import semver from 'semver';
|
||||
import sortedIndex from 'sortedindex-compare';
|
||||
|
||||
import Store from '../core/Store.js';
|
||||
import Store from '../lib/Store.js';
|
||||
|
||||
import actions from '../actions/appActions.js';
|
||||
|
||||
|
@ -16,7 +16,7 @@ class ProjectsStore extends Store {
|
|||
|
||||
// Initial payload.
|
||||
constructor() {
|
||||
// Init the projects.
|
||||
// Init the projects from local storage.
|
||||
let list = lscache.get('projects') || [];
|
||||
|
||||
super({
|
||||
|
@ -32,17 +32,13 @@ class ProjectsStore extends Store {
|
|||
|
||||
// Listen to only projects actions.
|
||||
actions.on('projects.*', (obj, event) => {
|
||||
let fn = ('on.' + event).replace(/[.]+(\w|$)/g, (m, p) => {
|
||||
return p.toUpperCase();
|
||||
});
|
||||
|
||||
let fn = `on.${event}`.replace(/[.]+(\w|$)/g, (m, p) => p.toUpperCase());
|
||||
// Run?
|
||||
(fn in this) && this[fn](obj);
|
||||
});
|
||||
|
||||
// Listen to when user is ready and save info on us.
|
||||
actions.on('user.ready', (user) => {
|
||||
this.set('user', user);
|
||||
});
|
||||
actions.on('user.ready', (user) => this.set('user', user));
|
||||
|
||||
// Persist projects in local storage (sans milestones and issues).
|
||||
this.on('list.*', () => {
|
||||
|
@ -304,7 +300,8 @@ class ProjectsStore extends Store {
|
|||
return _.findIndex(this.get('list'), { owner, name });
|
||||
}
|
||||
|
||||
// Save an error from loading milestones or issues
|
||||
// Save an error from loading milestones or issues.
|
||||
// TODO: clear these when we fetch all projects anew.
|
||||
saveError(project, err, say=false) {
|
||||
var idx;
|
||||
if ((idx = this.findIndex(project)) > -1) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import EventEmitter from '../src/js/core/EventEmitter.js';
|
||||
import EventEmitter from '../src/js/lib/EventEmitter.js';
|
||||
|
||||
export default {
|
||||
EventEmitter: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import Store from '../src/js/core/Store.js';
|
||||
import Store from '../src/js/lib/Store.js';
|
||||
|
||||
export default {
|
||||
Store: {
|
||||
|
|
|
@ -5,7 +5,7 @@ import opa from 'object-path';
|
|||
|
||||
import request from '../src/js/modules/github/request.js';
|
||||
import issues from '../src/js/modules/github/issues.js';
|
||||
import config from '../src/js/models/config.js';
|
||||
import config from '../src/config.js';
|
||||
|
||||
import json from './fixtures/issues.json';
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import opa from 'object-path';
|
|||
import { noCallThru } from 'proxyquire'
|
||||
let proxy = noCallThru();
|
||||
|
||||
import config from '../src/js/models/config.js';
|
||||
import config from '../src/config.js';
|
||||
|
||||
class Sa {
|
||||
constructor() {
|
||||
|
|
Loading…
Reference in New Issue