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:
parent
7ea8656104
commit
5dc37167ea
30
README.md
30
README.md
@ -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`
|
||||
|
@ -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
|
||||
|
138
webapp/app.jsx
138
webapp/app.jsx
@ -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')
|
||||
)
|
||||
|
@ -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 />
|
||||
}
|
||||
})
|
||||
}
|
||||
|
26
webapp/components/boardsapiwrapper.js
Normal file
26
webapp/components/boardsapiwrapper.js
Normal 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
|
64
webapp/components/getipfs.jsx
Normal file
64
webapp/components/getipfs.jsx
Normal 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>
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -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()
|
||||
|
49
webapp/components/navbar.jsx
Normal file
49
webapp/components/navbar.jsx
Normal 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>)
|
||||
}
|
||||
})
|
||||
}
|
12
webapp/components/options.jsx
Normal file
12
webapp/components/options.jsx
Normal 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
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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">
|
||||
|
26
webapp/components/postpage.jsx
Normal file
26
webapp/components/postpage.jsx
Normal 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 />
|
||||
}
|
||||
})
|
||||
}
|
@ -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 />
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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 />
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user