1
0
mirror of https://github.com/fazo96/ipfs-boards synced 2025-01-25 14:54:19 +01:00

made ipfs-api downloadable asynchroniously, better UX

This commit is contained in:
Enrico Fasoli 2015-11-22 00:10:46 +01:00
parent 7ea8656104
commit 5dc37167ea
17 changed files with 506 additions and 221 deletions

View File

@ -5,8 +5,6 @@ Image and discussion boards, forums and the like have many problems:
- Centralized
- What if it gets shut down?
- What if the servers are down?
- What if a phisical network link breaks and the data is on the other side?
- What if the owners of the board get eaten by aliens?
- Fragile
- what if there's a DoS attack?
@ -14,13 +12,6 @@ Image and discussion boards, forums and the like have many problems:
- what if the datacenter is flooded and data is lost?
- Closed Down, limited in possibilities
- What if I want to write a custom client and there is no API?
- What if I want to change the user interface?
- What if I want to use it in a LAN with no Internet access?
- What if I want a name that someone else already has?
- What if I want to move my data to another service/subreddit/forum ?
- What if I want more control (think a private forum), or less control (think 4chan) ?
- What if I want <feature> ?
This project was conceived to solve that. With the help of modern web technologies, the IPFS and IPNS protocols
and some optional cache servers, we can solve these problems and create a true universal platform which can act as:
@ -35,17 +26,14 @@ and some optional cache servers, we can solve these problems and create a true u
With security, control, reliability, rock solid stability, fully distributed
architecture or, optionally, none of these!
## FAQ
## Get Started
See `FAQ.md`
The App is not ready yet, but you're welcome to take a look at the prototype,
even though it only has informational pages and very limited functionality.
### How does it work?
You can access it [here](http://ipfs.io/ipfs/QmSqHvZDxiZFEy1AY6BXmogSk6DZiVJRUU4HEm7DmXSqno)
See `PROTOCOL.md`
## Demo / Prototype
You can find a working build [here](http://ipfs.io/ipfs/QmSqHvZDxiZFEy1AY6BXmogSk6DZiVJRUU4HEm7DmXSqno).
### Prototype information
You need a local instance of go-ipfs running for it to work. You also need to set
CORS settings right or it won't work. However, in that case, it will complain to
@ -70,6 +58,14 @@ Ability to publish stuff in the browser won't be implemented until go-ipfs 0.4
is ready. It will maybe be ready before the new year.
You will be able to publish your boards/profile/posts using a CLI though.
## FAQ
See `FAQ.md`
### How does it work?
See `PROTOCOL.md`
### How do I set up a development environment?
See `HACKING.md`

View File

@ -163,6 +163,7 @@ BoardsAPI.prototype.searchUsers = function(){
})
})
// Look for who has the correct version file, they probably have a profile
/*
this.ipfs.dht.findprovs(this.version_hash, (err,res) => {
if(err){
console.log('DHT FINDPROVS err',err)
@ -171,7 +172,7 @@ BoardsAPI.prototype.searchUsers = function(){
} else {
console.log('DHT FINDPROVS string',res)
}
})
})*/
return this.ee
}
@ -258,17 +259,20 @@ BoardsAPI.prototype.getBoardSettings = function(userID,board){
return this.ee
}
BoardsAPI.prototype.downloadPost = function(hash,adminID,board,op){
BoardsAPI.prototype.downloadPost = function(hash,adminID,board,op,done){
this.ipfs.cat(hash,(err2,r) => {
if(err2){
this.ee.emit('error',err2)
console.log('Could not download post',hash,'of',board+'@'+adminID)
if(done && done.apply) done(err2)
} else {
// It already returns a JSON?
var post = r
post.hash = hash
if(op) post.op = op // Inject op
if(board) this.ee.emit('post in '+board+'@'+adminID,post,hash)
this.ee.emit(hash,post,adminID,board)
if(done && done.apply) done(null,post)
}
})
return this.ee
@ -344,11 +348,13 @@ BoardsAPI.prototype.getCommentsFor = function(parent,board,adminID){
// Initialize API
BoardsAPI.prototype.init = function(done){
if(this.isInit) return
this.ipfs.id( (err, res) => {
if(err){
console.log('Error while getting OWN ID:',err)
this.ee.emit('error',err)
done(err)
this.ee.emit('init',err)
if(done && done.apply) done(err)
} else if(res.ID){
console.log('I am',res.ID)
this.id = res.ID
@ -358,25 +364,30 @@ BoardsAPI.prototype.init = function(done){
if(err2){
this.ee.emit('error',err2)
console.log('Error while calculating version hash:',err2)
done(err2)
this.ee.emit('init',err2)
if(done && done.apply) done(err2)
} else {
if(r && r.Hash) this.version_hash = r.Hash
if(r && r[0] && r[0].Hash) this.version_hash = r[0].Hash
console.log('Version hash is',this.version_hash)
done(null)
this.ipfs.version((err,res) => {
if(err){
this.ee.emit('error',err)
this.ee.emit('init',err)
console.log('Error while getting ipfs version:',err)
if(done && done.apply) done(err)
} else {
this.ipfs_version = res.Version
console.log('IPFS Version is',res.Version)
this.ee.emit('init',undefined)
this.isInit = true
if(done && done.apply) done(null)
}
})
}
})
}
})
this.ipfs.version((err,res) => {
if(err){
this.ee.emit('error',err)
console.log('Error while getting ipfs version:',err)
} else {
this.ipfs_version = res.Version
console.log('IPFS Version is',res.Version)
}
})
}
BoardsAPI.prototype.getEventEmitter = function(){
@ -387,4 +398,8 @@ BoardsAPI.prototype.getUsers = function(){
return Object.keys(this.users)
}
BoardsAPI.prototype.getMyID = function(){
return this.id
}
module.exports = BoardsAPI

View File

@ -5,7 +5,6 @@ var Route = require('react-router').Route
var IndexRoute = require('react-router').IndexRoute
var Redirect = require('react-router').Redirect
var Link = require('react-router').Link
var BoardsAPI = require('boards-api.js')
// Load CSS
require('normalize.css')
@ -14,17 +13,24 @@ require('style.css')
require('raleway.css')
require('font-awesome.min.css')
var opt, s = localStorage.getItem('ipfs-boards-settings')
try {
opt = JSON.parse(s)
} catch(e){
// Do nothing
}
if(opt === null || opt === undefined) opt = { addr: 'localhost', port: 5001 }
var ipfs = require('ipfs-api')(opt.addr || 'localhost',opt.port || 5001)
var boards = new BoardsAPI(ipfs)
// Load Components
// Components
var opt = require('options.jsx').get()
var boardsWrapper = require('boardsapiwrapper.js')
var boards = new boardsWrapper()
var Icon = require('icon.jsx')
var GetIPFS = require('getipfs.jsx')
// Load pages
var Navbar = require('navbar.jsx')(boards)
var Users = require('users.jsx')(boards)
var Settings = require('settings.jsx')(boards)
var Profile = require('profile.jsx')(boards)
var Board = require('board.jsx')(boards)
var PostPage = require('postpage.jsx')(boards)
// Define Main Components
var Container = React.createClass({
render: function(){
@ -38,32 +44,6 @@ var App = React.createClass({
}
})
var Navbar = React.createClass({
extraButtons: function(){
if(boards.id && boards.version){
return <span>
<Link className="nounderline" to="/@me"><Icon name="user" className="fa-2x light"/></Link>
<Link className="nounderline" to="/users"><Icon name="globe" className="fa-2x light"/></Link>
</span>
} else {
return <Link className="nounderline" to="/error"><Icon name="ban" className="fa-2x light"/></Link>
}
},
render: function(){
return (
<div className="navbar">
<div className="container">
{this.props.children || <h4><Link to="/"><Icon name="comments" className="light"/> Boards</Link></h4>}
<div className="u-pull-right iconbar">
{this.extraButtons()}
<Link className="nounderline" to="/settings"><Icon name="cog" className="fa-2x light"/></Link>
<a className="nounderline" href="https://github.com/fazo96/ipfs-boards"><Icon name="github" className="fa-2x light"/></a>
</div>
</div>
</div>)
}
})
// Static pages
var Static = React.createClass({
@ -85,29 +65,6 @@ var Homepage = React.createClass({
}
})
var GetIPFS = React.createClass({
render: function(){
return (
<div className="">
<h1><Icon name="ban"/> Connection to IPFS not available</h1>
<h4 className="light">Sorry, but at the moment an external application is needed to try the Prototype</h4>
<p>You don't have an IPFS node running at <code>{opt.addr}:{opt.port}</code> or it is not reachable</p>
<p>The IPFS Boards prototype requires a full IPFS node running at localhost.
Please start one by following the
<a href="https://github.com/ipfs/go-ipfs"><code>go-ipfs</code> documentation.</a></p>
<h5>Do you have a running node but the app won't work?</h5>
<p>It's probably one of these issues:</p>
<ul>
<li>Your IPFS node doesn't allow requests from the domain you're running the app from (CORS issue). See <a href="https://github.com/fazo96/ipfs-board/blob/master/ipfs_daemon_set_cors.sh">here</a> for the fix.</li>
<li>Your IPFS node is not listening for API requests at <code>{opt.addr}:{opt.port}</code>. Go to the <Link to="/settings">Settings page</Link>, provide the correct address for the node, then save and reload the page.</li>
<li>Some other networking issue is preventing the App from talking to your node.</li>
</ul>
<p>Still can't fix it? <a href="https://github.com/fazo96/ipfs-board/issues">File a issue on GitHub</a>, we'll be happy to help!</p>
</div>
)
}
})
var NotFound = React.createClass({
render: function(){
return (<div className="text-center">
@ -117,51 +74,22 @@ var NotFound = React.createClass({
}
})
var NotImplemented = React.createClass({
render: function(){
return ( <div className="text-center">
<h1>Not yet implemented</h1>
<h1><Icon name="cog" className="fa-spin"/></h1>
<p>Sorry, working on it!</p>
</div> )
}
})
// Start
var Users = require('users.jsx')(boards)
var Settings = require('settings.jsx')(boards)
var Profile = require('profile.jsx')(boards)
var Board = require('board.jsx')(boards)
var Icon = require('icon.jsx')
boards.init(err => {
if(err){
console.log('FATAL: IPFS NODE NOT AVAILABLE')
ReactDOM.render(
<Router>
<Route path="/" component={App}>
<IndexRoute component={Homepage} />
<Route path="/settings" component={Settings} />
<Route path="*" component={GetIPFS} />
ReactDOM.render(
<Router>
<Route path="/" component={App}>
<IndexRoute component={Homepage} />
<Route path="/@:userid">
<IndexRoute component={Profile} />
<Route path=":boardname">
<IndexRoute component={Board} />
<Route path=":posthash" component={PostPage} />
</Route>
</Router>
, document.getElementById('root'))
} else {
ReactDOM.render(
<Router>
<Route path="/" component={App}>
<IndexRoute component={Homepage} />
<Redirect from="/@me" to={'/@'+boards.id} />
<Route path="/@:userid">
<IndexRoute component={Profile} />
<Route path=":boardname" component={Board} />
</Route>
<Route path="/users" component={Users} />
<Route path="/settings" component={Settings} />
<Route path="*" component={NotFound} />
</Route>
</Router>, document.getElementById('root')
)
}
})
</Route>
<Route path="/users" component={Users} />
<Route path="/settings" component={Settings} />
<Route path="*" component={NotFound} />
</Route>
</Router>, document.getElementById('root')
)

View File

@ -3,28 +3,58 @@ var Markdown = require('markdown.jsx')
var Link = require('react-router').Link
var Icon = require('icon.jsx')
module.exports = function(boards){
var UserID = require('userID.jsx')(boards)
var PostList = require('postlist.jsx')(boards)
module.exports = function(boardsAPI){
var UserID = require('userID.jsx')(boardsAPI)
var PostList = require('postlist.jsx')(boardsAPI)
var GetIPFS = require('getipfs.jsx')(boardsAPI)
return React.createClass({
getInitialState: function(){
return { name: this.props.params.boardname }
return { name: this.props.params.boardname, api: false }
},
componentDidMount: function(){
var ee = boards.getBoardSettings(this.props.params.userid,this.props.params.boardname)
ee.on('settings for '+this.props.params.boardname+'@'+this.props.params.userid, (res) => {
if(!this.isMounted()) return true
console.log('Found name:',res.fullname)
this.setState({ name: res.fullname.trim(), description: res.description })
boardsAPI.use(boards => {
/*
When a component inside the component being rendered by the router also needs
access to the boards api, it appears unitialized and never initializes to it
for no apparent reason. Calling init twice (one automgically and one
when the root component mounts) works as a cheap, horrible workaround
*/
boards.init()
if(!this.isMounted()) return
var ee = boards.getEventEmitter()
ee.on('init',err => {
if(!err && this.isMounted()){
this.setState({ api: true })
this.init(boards)
}
})
ee.on('settings for '+this.props.params.boardname+'@'+this.props.params.userid, (res) => {
if(!this.isMounted()) return true
console.log('Found name:',res.fullname)
this.setState({ name: res.fullname.trim(), description: res.description })
})
if(boards.isInit || this.state.api){
this.setState({api: true})
this.init(boards)
boards.getBoardSettings(this.props.params.userid,this.props.params.boardname)
}
})
},
init: function(boards){
if(!this.state.init){
boards.getBoardSettings(this.props.params.userid,this.props.params.boardname)
this.setState({ init: true })
}
},
render: function(){
return (<div className="board">
<h2>{this.state.name}</h2>
<Markdown source={this.state.description} skipHtml={true} />
<h5><UserID id={this.props.params.userid} /></h5>
<PostList board={this.props.params.boardname} admin={this.props.params.userid}/>
</div>)
if(this.state.api){
return (<div className="board">
<h2>{this.state.name}</h2>
<Markdown source={this.state.description} skipHtml={true} />
<h5><UserID id={this.props.params.userid} /></h5>
<PostList board={this.props.params.boardname} admin={this.props.params.userid}/>
</div>)
} else return <GetIPFS />
}
})
}

View File

@ -0,0 +1,26 @@
var BoardsAPI = function(){
this.done = false
this.fa = []
this.boards
require.ensure(['options.jsx','ipfs-api','boards-api.js'], _ => {
var opt = require('options.jsx').get()
var BoardsAPI = require('boards-api.js')
var ipfs = require('ipfs-api')(opt.addr || 'localhost',opt.port || 5001)
this.boards = new BoardsAPI(ipfs)
this.boards.init()
this.done = true
this.fa.forEach(fn => fn(this.boards))
this.fa = undefined
})
}
BoardsAPI.prototype.use = function(f){
if(!f || !f.apply || !f.call) return console.log('Non-function tried to use API:',f)
if(this.done){
f(this.boards)
} else {
this.fa.push(f)
}
}
module.exports = BoardsAPI

View File

@ -0,0 +1,64 @@
var React = require('react')
var Link = require('react-router').Link
var Icon = require('icon.jsx')
module.exports = function(boardsAPI){
return React.createClass({
getInitialState: function(){
return { connected: false, error: false, long: false }
},
componentDidMount: function(){
boardsAPI.use(boards => {
if(!this.isMounted()) return
if(boards.isInit){
this.setState({ connected: true })
} else {
setTimeout(_ => {
if(this.isMounted()) this.setState({ long: true })
}, 5000)
boards.getEventEmitter().on('init', err => {
if(!this.isMounted()) return
if(err){
this.setState({ error: true })
} else {
this.setState({ connected: true })
}
})
}
})
},
render: function(){
var opt = require('options.jsx').get()
if(this.state.error){
return (
<div className="">
<h1><Icon name="ban"/> Connection to IPFS not available</h1>
<h4 className="light">Sorry, but at the moment an external application is needed to try the Prototype</h4>
<p>You don't have an IPFS node running at <code>{opt.addr}:{opt.port}</code> or it is not reachable.
The IPFS Boards prototype requires a full IPFS node. Please start one by following the
<a href="https://github.com/ipfs/go-ipfs"><code>go-ipfs</code> documentation.</a></p>
<h5>Do you have a running node but the app won't work?</h5>
<p>It's probably one of these issues:</p>
<ul>
<li>Your node is not located at <code>{opt.addr}:{opt.port}</code>. Go to the <Link to="/settings">Settings Page</Link> to configure the connection.</li>
<li>You edited your settings and saved them but didn't reload the page</li>
<li>Your IPFS node doesn't allow requests from the domain you're running the app from (CORS issue). See <a href="https://github.com/fazo96/ipfs-board/blob/master/ipfs_daemon_set_cors.sh">here</a> for the fix.</li>
<li>Some other networking or browser security issue is preventing the App from talking to your node.</li>
</ul>
<p>Still can't fix it? <a href="https://github.com/fazo96/ipfs-board/issues">File a issue on GitHub</a>, we'll be happy to help!</p>
</div>
)} else if(this.state.connected){
return <div class="text-center">
<h1><Icon name="check" /></h1>
<h5 class="light">You're connected!</h5>
</div>
} else {
return <div className="center-block text-center">
<Icon name="refresh" className="fa-3x center-block light fa-spin" />
<h4>Connecting to IPFS</h4>
{this.state.long?(<p>It's taking long... there's probably something wrong</p>):<p></p>}
</div>
}
}
})
}

View File

@ -1,11 +1,23 @@
var React = require('react')
var MarkdownLib = require('react-markdown')
module.exports = React.createClass({
getInitialState: function(){
return { lib: false }
},
componentDidMount: function(){
require.ensure(['react-markdown'],_ => {
if(this.isMounted()) this.setState({ MarkdownLib: require('react-markdown') })
})
},
renderIfApplicable: function(){
if(this.props.source)
return <MarkdownLib source={this.props.source} skipHtml={true} />
return <p>...</p>
if(this.props.source){
if(this.state.MarkdownLib){
var MarkdownLib = this.state.MarkdownLib
return <MarkdownLib source={this.props.source} skipHtml={true} />
} else {
return <p>{this.props.source}</p>
}
} else return <p>...</p>
},
render: function(){
return this.renderIfApplicable()

View File

@ -0,0 +1,49 @@
var React = require('react')
var Icon = require('icon.jsx')
var Link = require('react-router').Link
module.exports = function(boardsAPI){
return React.createClass({
getInitialState: function(){
return { api: false, loading: true }
},
componentDidMount(){
boardsAPI.use(boards => {
if(boards.isInit) this.setState({ api: true })
boards.getEventEmitter().on('init',err => {
if(!this.isMounted()) return
if(err){
this.setState({ loading: false, api: false })
} else {
this.setState({ api: true })
}
})
})
},
extraButtons: function(){
if(this.state.api){
return <span>
<Link className="nounderline" to="/@me"><Icon name="user" className="fa-2x light"/></Link>
<Link className="nounderline" to="/users"><Icon name="globe" className="fa-2x light"/></Link>
</span>
} else if(this.state.loading){
return <Icon name="refresh" className="fa-2x fa-spin light"/>
} else {
return <Link className="nounderline" to="/users"><Icon name="ban" className="fa-2x light"/></Link>
}
},
render: function(){
return (
<div className="navbar">
<div className="container">
{this.props.children || <h4><Link to="/"><Icon name="comments" className="light"/> Boards</Link></h4>}
<div className="u-pull-right iconbar">
{this.extraButtons()}
<Link className="nounderline" to="/settings"><Icon name="cog" className="fa-2x light"/></Link>
<a className="nounderline" href="https://github.com/fazo96/ipfs-boards"><Icon name="github" className="fa-2x light"/></a>
</div>
</div>
</div>)
}
})
}

View File

@ -0,0 +1,12 @@
module.exports = {
get: function(){
var opt, s = localStorage.getItem('ipfs-boards-settings')
try {
opt = JSON.parse(s)
} catch(e){
// Do nothing
}
if(opt === null || opt === undefined) opt = { addr: 'localhost', port: 5001 }
return opt
}
}

View File

@ -1,18 +1,28 @@
var React = require('react')
var moment = require('moment')
var Markdown = require('markdown.jsx')
var Icon = require('icon.jsx')
var Link = require('react-router').Link
module.exports = function(boards){
var UserID = require('userID.jsx')(boards)
module.exports = function(boardsAPI){
var UserID = require('userID.jsx')(boardsAPI)
return React.createClass({
getDate: function(){
if(this.props.post.date){
return moment.unix(this.props.post.date).fromNow()
if(this.state.moment)
return this.state.moment.unix(this.props.post.date).fromNow()
else return '...'
} else {
return 'Unknown Date'
}
},
getInitialState: function(){
return { moment: false }
},
componentDidMount: function(){
require.ensure(['moment'],_ => {
if(this.isMounted()) this.setState({ moment: require('moment') })
})
},
render: function(){
return <div key={this.props.post.title} className="post">
<div className="content">
@ -21,7 +31,8 @@ module.exports = function(boards){
<div className="icons">
<UserID id={this.props.post.op}></UserID>
<Icon name="clock-o" className="not-first"/> {this.getDate()}
<Icon name="comments" className="not-first" /> Comments
<Icon name="comments" className="not-first" />
<Link to={this.props.link || '/post/'+this.props.post.hash }>Comments</Link>
</div>
</div>
</div>

View File

@ -1,22 +1,21 @@
var React = require('react')
var moment = require('moment')
var sortedIndex = require('lodash.sortedindex')
module.exports = function(boards){
var Post = require('post.jsx')(boards)
module.exports = function(boardsAPI){
var Post = require('post.jsx')(boardsAPI)
return React.createClass({
getInitialState: function(){
return { posts: [] }
return { posts: [], api: false }
},
sortFn: function(a,b){
return (b.date || 0) - (a.date || 0)
},
componentDidMount: function(){
console.log('Initial POSTS',this.state.posts.length)
init: function(boards){
this.setState({ api: true })
boards.getPostsInBoard(this.props.admin,this.props.board)
.on('post in '+this.props.board+'@'+this.props.admin,(post,hash) => {
if(!this.isMounted()) return true
var now = moment().unix()
var now = (new Date()).getTime()
var posts = this.state.posts
if(post.date === undefined || post.date <= 0){
posts.push(post)
@ -29,6 +28,23 @@ module.exports = function(boards){
this.setState({ posts })
})
},
componentDidMount: function(){
boardsAPI.use(boards => {
if(boards.isInit) this.init(boards)
else boards.getEventEmitter().on('init',err => {
if(!err && this.isMounted()) this.init(boards)
})
})
},
getPosts: function(){
if(this.state.posts.length > 0 || this.state.api){
return this.state.posts.map(post => {
return <Post key={post.title+post.text} post={post} />
})
} else return <div className="center-block text-center">
<Icon name="refresh" className="fa-3x center-block light fa-spin" />
</div>
},
render: function(){
return (
<div className="postList">

View File

@ -0,0 +1,26 @@
var React = require('react')
var Post = require('post.jsx')
module.exports = function(boards){
var UserID = require('userID.jsx')(boards)
return React.createClass({
getInitialState: function(){
return { post: { title: '...', text: '...' }, api: boards.isInit }
},
componentDidMount: function(){
boards.getEventEmitter().on('init', _ => { if(this.isMounted()) this.setState({ api: true })})
boards.downloadPost(this.props.id,this.props.admin,this.props.board,this.props.op,(err,post) => {
if(err){
this.setState({ post: { title: 'Error', text: err.Message || err }})
} else {
this.setState({ post })
}
})
},
render: function(){
if(this.state.api || boards.isInit)
return <Post post={this.state.post} />
else return <GetIPFS />
}
})
}

View File

@ -3,32 +3,58 @@ var Markdown = require('markdown.jsx')
var Link = require('react-router').Link
var Icon = require('icon.jsx')
module.exports = function(boards){
module.exports = function(boardsAPI){
var GetIPFS = require('getipfs.jsx')(boardsAPI)
return React.createClass({
getInitialState: function(){
return { name: '...', boards: [] }
return { name: '...', boards: [], api: false }
},
componentDidMount: function(){
console.log('About to ask for profile for',this.props.params.userid)
var ee = boards.getEventEmitter()
ee.on('boards for '+this.props.params.userid,l => {
if(!this.isMounted()) return true
this.setState({ boards: l })
boardsAPI.use(boards => {
if(boards.isInit){
this.setState({ api: true, id: boards.id })
this.init()
}
var ee = boards.getEventEmitter()
ee.on('init',err => {
if(!err && this.isMounted()){
this.setState({ api: true, id: boards.id })
this.init()
}
})
})
boards.getProfile(this.props.params.userid,(err,res) => {
if(!this.isMounted()) return true
if(err){
this.setState({
name: <Icon name="ban" />,
description: err
},
init: function(){
if(this.state.init) return
boardsAPI.use(boards => {
var ee = boards.getEventEmitter()
if(boards.isInit || this.state.api){
var uid = this.props.params.userid
if(uid === 'me') uid = boards.id
console.log('About to ask for profile for',uid)
ee.on('boards for '+uid,l => {
if(!this.isMounted()) return true
this.setState({ boards: l })
})
} else {
this.setState({ name: res.name, description: res.description })
boards.getProfile(uid,(err,res) => {
if(!this.isMounted()) return true
if(err){
this.setState({
name: <Icon name="ban" />,
description: err
})
} else {
this.setState({ name: res.name, description: res.description })
}
})
this.setState({ init: true })
}
})
},
linkToEditor: function(){
if(this.props.params.userid === boards.id){
var uid = this.props.params.userid
if(uid === 'me' && this.state.id) uid = this.state.id
if(uid === this.state.id){
return <div>
<h6>This is your profile</h6>
<hr/>
@ -37,18 +63,22 @@ module.exports = function(boards){
return ''
},
render: function(){
return (<div className="profile">
{this.linkToEditor()}
<h1>{this.state.name}</h1>
<Markdown source={this.state.description} skipHtml={true} />
<hr/>
<h5 className="light">@{this.props.params.userid}</h5>
{this.state.boards.map(n => {
return <h6 className="light" key={this.props.params.userid+'/'+n.name}>
<Link to={'/@'+this.props.params.userid+'/'+n.name}># {n.name}</Link>
</h6>
})}
</div>)
if(this.state.api){
var uid = this.props.params.userid
if(uid === 'me') uid = this.state.id
return (<div className="profile">
{this.linkToEditor()}
<h1>{this.state.name}</h1>
<Markdown source={this.state.description} skipHtml={true} />
<hr/>
<h5 className="light">@{uid}</h5>
{this.state.boards.map(n => {
return <h6 className="light" key={uid+'/'+n.name}>
<Link to={'/@'+uid+'/'+n.name}># {n.name}</Link>
</h6>
})}
</div>)
} else return <GetIPFS />
}
})
}

View File

@ -1,12 +1,18 @@
var React = require('react')
var Icon = require('icon.jsx')
module.exports = function(boards){
module.exports = function(boardsAPI){
return React.createClass({
getDefaults: function(){
return { addr: 'localhost', port: 5001 }
return { addr: 'localhost', port: 5001, api: false }
},
getInitialState: function(){
boardsAPI.use(boards => {
if(boards.isInit && this.isMounted()) this.setState({ api: true })
boards.getEventEmitter().on('init', err => {
if(!err && this.isMounted()) this.setState({ api: true })
})
})
var s = localStorage.getItem('ipfs-boards-settings')
var obj = this.getDefaults()
try {
@ -24,7 +30,7 @@ module.exports = function(boards){
addr: this.state.addr,
port: parseInt(this.state.port)
}))
alert('Saved')
window.location.reload(false)
}
},
setDefaults: function(){
@ -37,6 +43,14 @@ module.exports = function(boards){
this.setState({ port: event.target.value })
}
},
isOK: function(){
if(this.state.api){
return <div className="itsok light">
<h5><Icon name="check" /> It's OK</h5>
<p>You're connected to IPFS</p>
</div>
}
},
render: function(){
return (
<div className="settings">
@ -54,6 +68,7 @@ module.exports = function(boards){
<input className="u-full-width" type="text" id="nodePort" value={this.state.port} onChange={this.onChange} placeholder="5001" />
</div>
</div>
{this.isOK()}
<div className="buttons">
<button className="button button-primary" onClick={this.save}>Save</button>
<button className="button not-first" onClick={this.setDefaults}>Defaults</button>

View File

@ -2,15 +2,29 @@ var React = require('react')
var Icon = require('icon.jsx')
var Link = require('react-router').Link
module.exports = function(boards){
module.exports = function(boardsAPI){
return React.createClass({
getInitialState: function(){
return { }
},
componentDidMount: function(){
if(this.props.id) boards.getProfile(this.props.id, (err,res) => {
boardsAPI.use(boards => {
if(boards.isInit){
this.getProfile(boards)
}
boards.getEventEmitter().on('init',err => {
if(!err) this.getProfile(boards)
else console.log('ERR INIT',err)
})
})
},
getProfile: function(boards){
if(this.props.id === undefined) return
boards.getProfile(this.props.id, (err,res) => {
if(!this.isMounted()) return true
if(!err) {
if(err){
console.log('Error while resolving user badge:',err)
} else {
this.setState({ name: res.name || 'Unknown Name' })
}
})
@ -23,15 +37,16 @@ module.exports = function(boards){
}
},
render: function(){
if(this.props.id)
if(this.props.id === undefined || this.props.id === 'undefined')
return <div className="user-id">
<Icon name="ban" /> Unknown User
</div>
else
return (<div className="user-id">
<Link className="light nounderline" to={'/@'+this.props.id}>
{this.getContent()}{this.state.name || this.props.id}
</Link>
</div>)
else return <div className="user-id">
<Icon name="ban" /> Unknown User
</div>
}
})
}

View File

@ -1,29 +1,55 @@
var React = require('react')
var Icon = require('icon.jsx')
module.exports = function(boards){
var UserID = require('userID.jsx')(boards)
module.exports = function(boardsAPI){
var GetIPFS = require('getipfs.jsx')(boardsAPI)
var UserID = require('userID.jsx')(boardsAPI)
return React.createClass({
getInitialState: function(){
return { users: boards.getUsers() }
return { users: [], api: false }
},
componentDidMount: function(){
boards.searchUsers().on('user',(id) => {
if(id === undefined) console.log('found undefined user???')
if(this.isMounted() && this.state.users.indexOf(id) < 0)
this.setState({ users: this.state.users.concat(id) })
boardsAPI.use(boards => {
boards.init()
if(boards.isInit){
if(this.isMounted()){
this.setState({ api: true })
this.init(boards)
}
}
var ee = boards.getEventEmitter()
ee.on('init', e => {
if(!e && this.isMounted()){
this.init(boards)
this.setState({ api: true })
}
})
ee.on('user',(id) => {
if(id === undefined || id === 'undefined') console.log('found undefined user???')
if(this.isMounted() && this.state.users.indexOf(id) < 0){
this.setState({ users: this.state.users.concat(id) })
}
})
})
},
init: function(boards){
if(this.isMounted() && !this.state.init){
this.setState({ users: boards.getUsers(), init: true })
boards.searchUsers()
}
},
render: function(){
return <div>
<h1><Icon name="users" /> Users</h1>
<p>Found <b>{this.state.users.length}</b> users</p>
<ul>
{this.state.users.map(user => {
return <UserID key={user} id={user} />
})}
</ul>
</div>
if(this.state.api){
return <div>
<h1><Icon name="users" /> Users</h1>
<p>Found <b>{this.state.users.length}</b> users</p>
<ul>
{this.state.users.map(user => {
return <UserID key={user} id={user} />
})}
</ul>
</div>
} else return <GetIPFS />
}
})
}

View File

@ -20,6 +20,11 @@ a {
text-decoration: none;
}
.center-block {
margin:auto;
display:block;
}
.navbar {
position: fixed;
width: 100%;
@ -111,6 +116,15 @@ a:hover {
text-decoration: none;
}
.itsok p, h5 {
display: inline-block;
margin-bottom: 1rem;
}
.itsok h5 {
margin-right: 1rem;
}
@media (min-width: 400px) {
/* larger than mobile */
.settings .buttons {