2015-11-14 01:34:03 +01:00
var React = require ( 'react' )
var ReactDOM = require ( 'react-dom' )
2015-11-14 13:12:48 +01:00
var Router = require ( 'react-router' ) . Router
var Route = require ( 'react-router' ) . Route
var IndexRoute = require ( 'react-router' ) . IndexRoute
2015-11-18 15:40:29 +01:00
var Redirect = require ( 'react-router' ) . Redirect
2015-11-14 13:12:48 +01:00
var Link = require ( 'react-router' ) . Link
2015-11-17 20:32:52 +01:00
var MarkdownLib = require ( 'react-markdown' )
2015-11-19 16:51:04 +01:00
var moment = require ( 'moment' )
var sortedIndex = require ( 'lodash.sortedindex' )
2015-11-14 12:06:37 +01:00
var BoardsAPI = require ( '../lib/boards-api.js' )
2015-11-18 00:37:24 +01:00
var opt , s = localStorage . getItem ( 'ipfs-boards-settings' )
try {
opt = JSON . parse ( s )
} catch ( e ) {
2015-11-18 01:55:31 +01:00
// Do nothing
2015-11-18 00:37:24 +01:00
}
2015-11-18 01:55:31 +01:00
if ( opt === null || opt === undefined ) opt = { addr : 'localhost' , port : 5001 }
2015-11-18 00:37:24 +01:00
var ipfs = require ( 'ipfs-api' ) ( opt . addr || 'localhost' , opt . port || 5001 )
2015-11-14 12:06:37 +01:00
var boards = new BoardsAPI ( ipfs )
2015-11-17 20:32:52 +01:00
// Components
var Markdown = React . createClass ( {
renderIfApplicable : function ( ) {
if ( this . props . source )
return < MarkdownLib source = { this . props . source } skipHtml = { true } / >
return < p > ... < / p >
} ,
2015-11-14 12:06:37 +01:00
render : function ( ) {
2015-11-17 20:32:52 +01:00
return this . renderIfApplicable ( )
2015-11-14 12:06:37 +01:00
}
} )
2015-11-17 20:32:52 +01:00
var Icon = React . createClass ( {
class : function ( ) {
2015-11-19 16:56:02 +01:00
return 'fa fa-' + this . props . name + ' ' + this . props . className
2015-11-17 20:32:52 +01:00
} ,
2015-11-14 12:06:37 +01:00
render : function ( ) {
2015-11-17 20:32:52 +01:00
return ( < i className = { this . class ( ) } > < / i > )
2015-11-14 12:06:37 +01:00
}
} )
2015-11-17 20:32:52 +01:00
var Container = React . createClass ( {
2015-11-14 12:06:37 +01:00
render : function ( ) {
2015-11-17 20:32:52 +01:00
return ( < div className = "container app" > { this . props . children } < / div > )
}
} )
var App = React . createClass ( {
render : function ( ) {
return ( < div > < Navbar / > < Container > { this . props . children } < / Container > < / div > )
2015-11-14 12:06:37 +01:00
}
} )
var Navbar = React . createClass ( {
render : function ( ) {
return (
< div className = "navbar" >
< div className = "container" >
2015-11-19 17:06:52 +01:00
{ this . props . children || < h4 > < Link to = "/" > < Icon name = "comments" className = "light" / > Boards < / Link > < / h4 > }
< div className = "u-pull-right iconbar" >
< 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 >
< 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 >
2015-11-17 20:32:52 +01:00
< / div >
2015-11-14 12:06:37 +01:00
< / div >
< / div > )
}
} )
2015-11-19 14:43:13 +01:00
var Post = React . createClass ( {
2015-11-19 16:51:04 +01:00
getDate : function ( ) {
if ( this . props . post . date ) {
return moment . unix ( this . props . post . date ) . fromNow ( )
} else {
return 'Unknown Date'
}
} ,
2015-11-19 14:43:13 +01:00
render : function ( ) {
return < div key = { this . props . post . title } className = "post" >
< div className = "content" >
< h5 > { this . props . post . title } < / h5 > < hr / >
< Markdown source = { this . props . post . text } skipHtml = { true } / > < hr / >
< div className = "icons" >
2015-11-19 16:51:04 +01:00
< UserID id = { this . props . post . op } > < / UserID >
2015-11-19 16:56:02 +01:00
< Icon name = "clock-o" className = "not-first" / > { this . getDate ( ) }
< Icon name = "comments" className = "not-first" / > Comments
2015-11-19 14:43:13 +01:00
< / div >
< / div >
< / div >
}
} )
2015-11-14 15:03:38 +01:00
var PostList = React . createClass ( {
2015-11-14 13:12:48 +01:00
getInitialState : function ( ) {
return { posts : [ ] }
} ,
2015-11-19 16:51:04 +01:00
sortFn : function ( a , b ) {
return ( b . date || 0 ) - ( a . date || 0 )
} ,
2015-11-14 15:03:38 +01:00
componentDidMount : function ( ) {
2015-11-14 18:26:56 +01:00
console . log ( 'Initial POSTS' , this . state . posts . length )
2015-11-16 14:35:27 +01:00
boards . getPostsInBoard ( this . props . admin , this . props . board )
. on ( 'post in ' + this . props . board + '@' + this . props . admin , ( post , hash ) => {
2015-11-14 18:26:56 +01:00
if ( ! this . isMounted ( ) ) return true
2015-11-19 16:51:04 +01:00
var now = moment ( ) . unix ( )
var posts = this . state . posts
if ( post . date === undefined || post . date <= 0 ) {
posts . push ( post )
} else if ( post . date <= now ) {
var i = sortedIndex ( posts , post , ( p ) => now - p . date || now )
posts . splice ( i , 0 , post )
} else {
console . log ( 'Post discarded cause date in the future:' , post )
}
this . setState ( { posts } )
2015-11-14 15:03:38 +01:00
} )
} ,
2015-11-14 13:12:48 +01:00
render : function ( ) {
return (
2015-11-14 15:03:38 +01:00
< div className = "postList" >
{ this . state . posts . map ( post => {
2015-11-19 14:43:13 +01:00
return < Post key = { post . title + post . text } post = { post } / >
2015-11-14 15:03:38 +01:00
} ) }
< / div >
2015-11-14 13:12:48 +01:00
)
}
} )
2015-11-14 16:26:03 +01:00
var UserID = React . createClass ( {
getInitialState : function ( ) {
2015-11-18 16:56:07 +01:00
return { }
2015-11-14 16:26:03 +01:00
} ,
componentDidMount : function ( ) {
2015-11-19 16:51:04 +01:00
if ( this . props . id ) boards . getProfile ( this . props . id , ( err , res ) => {
2015-11-14 18:26:56 +01:00
if ( ! this . isMounted ( ) ) return true
2015-11-14 16:26:03 +01:00
if ( ! err ) {
2015-11-17 20:32:52 +01:00
this . setState ( { name : res . name . trim ( ) } )
2015-11-14 16:26:03 +01:00
}
} )
} ,
2015-11-18 16:56:07 +01:00
getContent : function ( ) {
if ( this . state . name ) {
return ( < Icon name = "user" / > )
} else {
return '@'
}
} ,
2015-11-14 15:03:38 +01:00
render : function ( ) {
2015-11-19 16:51:04 +01:00
if ( this . props . id )
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 >
2015-11-14 16:26:03 +01:00
}
} )
2015-11-17 20:32:52 +01:00
// Static pages
var Homepage = React . createClass ( {
2015-11-14 16:26:03 +01:00
render : function ( ) {
2015-11-17 20:32:52 +01:00
return (
< div >
< h3 > Welcome to the IPFS Boards Prototype < / h3 >
< p > Not much is implemented ... < / p >
< p > You can try < Link to = "@QmXnfA41SXMX3tqFD4kjED7ehyvgTsuAho86TkEoTbZdpw" > Opening my Profile < / Link > though : ) < / p >
< p > More information about the project on < a href = "https://github.com/fazo96/ipfs-board" > GitHub < / a > < / p >
< / div >
)
2015-11-14 15:03:38 +01:00
}
} )
2015-11-14 23:33:50 +01:00
var GetIPFS = React . createClass ( {
render : function ( ) {
return (
2015-11-18 00:37:24 +01:00
< div className = "" >
< h1 > < Icon name = "ban" / > Missing IPFS Node < / h1 >
< p > You don ' t have an IPFS node running at < code > { opt . addr } : { opt . port } < / code > or it is not reachable < / p >
2015-11-14 23:33:50 +01:00
< 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 >
2015-11-18 00:37:24 +01:00
< 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 >
2015-11-14 23:33:50 +01:00
< 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 >
)
}
} )
2015-11-17 20:32:52 +01:00
var NotFound = React . createClass ( {
render : function ( ) {
return ( < div className = "text-center" >
< h1 > < Icon name = "ban" / > < / h1 >
< p > Sorry , there ' s nothing here ! < / p >
< / div > )
}
} )
var NotImplemented = React . createClass ( {
render : function ( ) {
return ( < div className = "text-center" >
< h1 > Not yet implemented < / h1 >
2015-11-19 16:56:02 +01:00
< h1 > < Icon name = "cog" className = "fa-spin" / > < / h1 >
2015-11-17 20:32:52 +01:00
< p > Sorry , working on it ! < / p >
< / div > )
}
} )
// Dynamic pages
var Profile = React . createClass ( {
getInitialState : function ( ) {
return { name : '...' , boards : [ ] }
} ,
componentDidMount : function ( ) {
2015-11-18 16:56:07 +01:00
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 } )
} )
boards . getProfile ( this . props . params . userid , ( err , res ) => {
2015-11-17 20:32:52 +01:00
if ( ! this . isMounted ( ) ) return true
if ( err ) {
this . setState ( {
2015-11-18 16:56:07 +01:00
name : < Icon name = "ban" / > ,
description : err
2015-11-17 20:32:52 +01:00
} )
} else {
this . setState ( { name : res . name , description : res . description } )
}
} )
} ,
2015-11-18 15:40:29 +01:00
linkToEditor : function ( ) {
if ( this . props . params . userid === boards . id ) {
return < div >
< h6 > This is your profile < / h6 >
< hr / >
< / div >
}
return ''
} ,
2015-11-17 20:32:52 +01:00
render : function ( ) {
return ( < div className = "profile" >
2015-11-18 15:40:29 +01:00
{ this . linkToEditor ( ) }
2015-11-17 20:32:52 +01:00
< 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 > )
}
} )
var Board = React . createClass ( {
getInitialState : function ( ) {
return { name : this . props . params . boardname }
} ,
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 } )
} )
} ,
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 > )
}
} )
var Users = React . createClass ( {
2015-11-18 16:56:07 +01:00
getInitialState : function ( ) {
2015-11-19 12:59:19 +01:00
return { users : boards . getUsers ( ) }
2015-11-18 16:56:07 +01:00
} ,
componentDidMount : function ( ) {
boards . searchUsers ( ) . on ( 'user' , ( id ) => {
2015-11-20 16:06:00 +01:00
if ( id === undefined ) console . log ( 'found undefined user???' )
2015-11-18 16:56:07 +01:00
if ( this . isMounted ( ) && this . state . users . indexOf ( id ) < 0 )
this . setState ( { users : this . state . users . concat ( id ) } )
} )
} ,
2015-11-17 20:32:52 +01:00
render : function ( ) {
2015-11-18 16:56:07 +01:00
return < div >
< h1 > < Icon name = "users" / > Users < / h1 >
2015-11-19 12:59:19 +01:00
< p > Found < b > { this . state . users . length } < / b > users < / p >
2015-11-18 16:56:07 +01:00
< ul >
{ this . state . users . map ( user => {
return < UserID key = { user } id = { user } / >
} ) }
< / ul >
< / div >
2015-11-17 20:32:52 +01:00
}
} )
var Settings = React . createClass ( {
2015-11-18 00:37:24 +01:00
getDefaults : function ( ) {
return { addr : 'localhost' , port : 5001 }
} ,
2015-11-17 22:18:30 +01:00
getInitialState : function ( ) {
2015-11-18 00:37:24 +01:00
var s = localStorage . getItem ( 'ipfs-boards-settings' )
var obj = this . getDefaults ( )
try {
obj = JSON . parse ( s )
} catch ( e ) {
localStorage . removeItem ( 'ipfs-boards-settings' )
}
return obj || this . getDefaults ( )
2015-11-17 22:18:30 +01:00
} ,
save : function ( ) {
2015-11-18 00:37:24 +01:00
if ( isNaN ( this . state . port ) || parseInt ( this . state . port ) > 65535 || parseInt ( this . state . port ) < 1 ) {
alert ( 'Port number invalid' )
} else {
localStorage . setItem ( 'ipfs-boards-settings' , JSON . stringify ( {
addr : this . state . addr ,
port : parseInt ( this . state . port )
} ) )
alert ( 'Saved' )
}
2015-11-17 22:18:30 +01:00
} ,
setDefaults : function ( ) {
2015-11-18 00:37:24 +01:00
this . setState ( this . getDefaults ( ) )
2015-11-17 22:18:30 +01:00
} ,
onChange : function ( event ) {
2015-11-18 00:37:24 +01:00
if ( event . target . id === 'nodeAddress' ) {
this . setState ( { addr : event . target . value } )
} else {
this . setState ( { port : event . target . value } )
}
2015-11-17 22:18:30 +01:00
} ,
2015-11-17 20:32:52 +01:00
render : function ( ) {
2015-11-17 22:18:30 +01:00
return (
< div className = "settings" >
< h2 > < Icon name = "cog" / > Settings < / h2 >
2015-11-18 00:37:24 +01:00
< h5 > This page is still a little rough , but it works . Reload the page after saving to apply changes . < / h5 >
2015-11-17 22:18:30 +01:00
< p > Use this page to customize the application ' s behavior . For now , you can change how it connects to IPFS . < / p >
< p > All settings are saved in your browser . < / p >
< div className = "row" >
< div className = "six columns" >
2015-11-19 17:37:50 +01:00
< label htmlFor = "nodeAddress" > IPFS Node < / label >
2015-11-18 00:37:24 +01:00
< input className = "u-full-width" type = "text" id = "nodeAddress" value = { this . state . addr } onChange = { this . onChange } placeholder = "localhost" / >
2015-11-17 22:18:30 +01:00
< / div >
< div className = "six columns" >
2015-11-19 17:37:50 +01:00
< label htmlFor = "nodePort" > API Port < / label >
2015-11-17 22:18:30 +01:00
< input className = "u-full-width" type = "text" id = "nodePort" value = { this . state . port } onChange = { this . onChange } placeholder = "5001" / >
< / div >
< / div >
< div className = "buttons" >
< button className = "button button-primary" onClick = { this . save } > Save < / button >
< button className = "button not-first" onClick = { this . setDefaults } > Defaults < / button >
< / div >
< / div >
)
2015-11-17 20:32:52 +01:00
}
} )
// Start
2015-11-14 23:33:50 +01:00
boards . init ( err => {
if ( err ) {
console . log ( 'FATAL: IPFS NODE NOT AVAILABLE' )
2015-11-18 00:37:24 +01:00
ReactDOM . render (
< Router >
< Route path = "/" component = { App } >
< IndexRoute component = { GetIPFS } / >
< Route path = "/settings" component = { Settings } / >
< / Route >
< / Router >
, document . getElementById ( 'root' ) )
2015-11-14 23:33:50 +01:00
} else {
ReactDOM . render (
< Router >
< Route path = "/" component = { App } >
< IndexRoute component = { Homepage } / >
2015-11-18 15:40:29 +01:00
< Redirect from = "/@me" to = { '/@' + boards . id } / >
2015-11-14 23:33:50 +01:00
< Route path = "/@:userid" >
< IndexRoute component = { Profile } / >
2015-11-17 20:32:52 +01:00
< Route path = ":boardname" component = { Board } / >
2015-11-14 23:33:50 +01:00
< / Route >
2015-11-17 20:32:52 +01:00
< Route path = "/users" component = { Users } / >
< Route path = "/settings" component = { Settings } / >
< Route path = "*" component = { NotFound } / >
2015-11-14 23:33:50 +01:00
< / Route >
< / Router > , document . getElementById ( 'root' )
)
}
} )