diff options
Diffstat (limited to 'src/web/js/components')
-rw-r--r-- | src/web/js/components/MiddlePanel.react.js | 12 | ||||
-rw-r--r-- | src/web/js/components/RightPanel.react.js | 252 | ||||
-rw-r--r-- | src/web/js/components/time.react.js | 15 |
3 files changed, 224 insertions, 55 deletions
diff --git a/src/web/js/components/MiddlePanel.react.js b/src/web/js/components/MiddlePanel.react.js index 6b3eb427..6bfdaaa9 100644 --- a/src/web/js/components/MiddlePanel.react.js +++ b/src/web/js/components/MiddlePanel.react.js @@ -1,6 +1,4 @@ var React = require('react'); -var ReactIntl = require('react-intl'); -var FormattedRelative = ReactIntl.FormattedRelative; var Row = require('react-bootstrap/Row'); var Button = require('react-bootstrap/Button'); @@ -11,8 +9,9 @@ var MiddlePanelStore = require('../stores/MiddlePanelStore'); var MiddlePanelActions = require('../actions/MiddlePanelActions'); var RightPanelActions = require('../actions/RightPanelActions'); +var JarrTime = require('./time.react'); + var TableLine = React.createClass({ - mixins: [ReactIntl.IntlMixin], propTypes: {article_id: React.PropTypes.number.isRequired, feed_title: React.PropTypes.string.isRequired, icon_url: React.PropTypes.string, @@ -49,11 +48,10 @@ var TableLine = React.createClass({ } // FIXME https://github.com/yahoo/react-intl/issues/189 // use FormattedRelative when fixed, will have to upgrade to ReactIntlv2 - var date = (<time dateTime={this.props.date} title={this.props.date}> - {this.formatRelative(this.props.timestamp)} - </time>); return (<div className={clsses} onClick={this.loadArticle}> - <h5><strong>{title}</strong></h5>{date} + <h5><strong>{title}</strong></h5> + <JarrTime text={this.props.date} + stamp={this.props.timestamp} /> <div>{read} {liked} {this.props.title}</div> </div> ); diff --git a/src/web/js/components/RightPanel.react.js b/src/web/js/components/RightPanel.react.js index a18f0ac3..2e2aac3e 100644 --- a/src/web/js/components/RightPanel.react.js +++ b/src/web/js/components/RightPanel.react.js @@ -1,70 +1,223 @@ var React = require('react'); var Col = require('react-bootstrap/Col'); -var Panel = require('react-bootstrap/Panel'); +var Glyphicon = require('react-bootstrap/Glyphicon'); +var Button = require('react-bootstrap/Button'); +var ButtonGroup = require('react-bootstrap/ButtonGroup'); + var RightPanelStore = require('../stores/RightPanelStore'); +var JarrTime = require('./time.react'); -var Article = React.createClass({ - propTypes: {article: React.PropTypes.object.isRequired}, - render: function() { +var PanelMixin = { + propTypes: {obj: React.PropTypes.object.isRequired}, + getHeader: function() { var icon = null; - if(this.props.article.icon_url){ - icon = (<img width="16px" src={this.props.article.icon_url} />); + if(this.props.obj.icon_url){ + icon = (<img width="16px" src={this.props.obj.icon_url} />); + } + var btn_grp = null; + if(this.isEditable() || this.isRemovable()) { + var edit_button = null; + if(this.isEditable()) { + edit_button = (<Button onClick={this.onClickEdit}> + <Glyphicon glyph="pencil" /> + </Button>); + } + var rem_button = null; + if(this.isRemovable()) { + rem_button = (<Button onClick={this.onClickRemove}> + <Glyphicon glyph="remove-sign" /> + </Button>); + } + btn_grp = (<ButtonGroup bsSize="small"> + {edit_button} + {rem_button} + </ButtonGroup>); } - var header = (<h4> - {icon}<strong>Title:</strong> {this.props.article.title} - </h4>); - return (<Panel header={header}> - <div dangerouslySetInnerHTML={{__html: this.props.article.content}} /> - </Panel> + return (<div id="right-panel-heading" className="panel-heading"> + <h4>{icon}<strong>Title:</strong> {this.getTitle()}</h4> + {btn_grp} + </div>); + }, + getKey: function(prefix, suffix) { + return ((this.state.edit_mode?'edit':'fix') + prefix + + '-' + this.props.obj.id + '-' + suffix); + }, + getCore: function() { + var items = []; + var key; + if(!this.state.edit_mode) { + this.fields.map(function(field) { + key = this.getKey('dt', field.key); + items.push(<dt key={key}>{field.title}</dt>); + key = this.getKey('dd', field.key); + if(field.type == 'string') { + items.push(<dd key={key}>{this.props.obj[field.key]}</dd>); + } else if(field.type == 'bool') { + if(this.props.obj[field.key]) { + items.push(<dd key={key}><Glyphicon glyph="ok" /></dd>); + } else { + items.push(<dd key={key}><Glyphicon glyph="pause" /></dd>); + } + } else if (field.type == 'link') { + items.push(<dd key={key}> + <a href={this.props.obj[field.key]}> + {this.props.obj[field.key]} + </a> + </dd>); + } + }.bind(this)); + } else { + this.fields.map(function(field) { + key = this.getKey('dd', field.key); + items.push(<dt key={key}>{field.title}</dt>); + key = this.getKey('dt', field.key); + if(field.type == 'string' || field.type == 'link') { + items.push(<dd key={key}><input type="text" + defaultValue={this.props.obj[field.key]} /> + </dd>); + } else if (field.type == 'bool') { + items.push(<dd key={key}><input type="checkbox" + defaultChecked={this.props.obj[field.key]} /></dd>); + } + }.bind(this)); + items.push(<dd key={this.getKey('dd', 'submit')}> + <button className="btn btn-default"> + Submit + </button> + </dd>); + } + return (<dl className="dl-horizontal">{items}</dl>); + }, + render: function() { + return (<div className="panel panel-default"> + {this.getHeader()} + {this.getBody()} + </div> ); }, + onClickEdit: function() { + this.setState({edit_mode: !this.state.edit_mode}); + }, + onClickRemove: function() { + }, +}; + +var Article = React.createClass({ + mixins: [PanelMixin], + getInitialState: function() {return {edit_mode: false};}, + fields: [], + isEditable: function() {return false;}, + isRemovable: function() {return true;}, + getTitle: function() {return this.props.obj.title;}, + getBody: function() { + return (<div className="panel-body" dangerouslySetInnerHTML={ + {__html: this.props.obj.content}} />); + }, }); var Feed = React.createClass({ - propTypes: {feed: React.PropTypes.object.isRequired}, - render: function() { - var icon = null; - if(this.props.feed.icon_url){ - icon = (<img width="16px" src={this.props.feed.icon_url} />); + mixins: [PanelMixin], + getInitialState: function() { + return {edit_mode: false, filters: this.props.obj.filters}; + }, + isEditable: function() {return true;}, + isRemovable: function() {return true;}, + fields: [{'title': 'Feed title', 'type': 'string', 'key': 'title'}, + {'title': 'Description', 'type': 'string', 'key': 'description'}, + {'title': 'Feed link', 'type': 'link', 'key': 'link'}, + {'title': 'Site link', 'type': 'link', 'key': 'site_link'}, + {'title': 'Enabled', 'type': 'bool', 'key': 'enabled'}, + ], + getTitle: function() {return this.props.obj.title;}, + getFilterRow: function(i, filter) { + return (<dd key={'d' + i + '-' + this.props.obj.id} + className="input-group filter-row"> + <span className="input-group-btn"> + <button className="btn btn-default" type="button" + data-index={i} onClick={this.removeFilterRow}> + <Glyphicon glyph='minus' /> + </button> + </span> + <select name="type" className="form-control" + defaultValue={filter.type}> + <option value='simple match'>simple match</option> + <option value='regex'>regex</option> + </select> + <input type="text" className="form-control" + name="pattern" defaultValue={filter.pattern} /> + <select name="action_on" className="form-control" + defaultValue={filter.action_on}> + <option value="match">match</option> + <option value="no match">no match</option> + </select> + <select name="action" className="form-control" + defaultValue={filter.action}> + <option value="mark as read">mark as read</option> + <option value="mark as favorite">mark as favorite</option> + </select> + </dd>); + }, + getBody: function() { + var filter_rows = []; + for(var i in this.state.filters) { + filter_rows.push(this.getFilterRow(i, this.state.filters[i])); } - var header = (<h4> - {icon}<strong>Title:</strong> {this.props.feed.title} - </h4>); - return (<Panel collapsible defaultExpanded header={header}> + return (<div className="panel-body"> <dl className="dl-horizontal"> - <dt>Description</dt> - <dd>{this.props.feed.description}</dd> <dt>Created on</dt> - <dd>{this.props.feed.created_date}</dd> - <dt>Feed adress</dt> - <dd> - <a href={this.props.feed.link}> - {this.props.feed.link} - </a> + <dd><JarrTime stamp={this.props.obj.created_stamp} + text={this.props.obj.created_date} /> + </dd> + <dt>Last fetched</dt> + <dd><JarrTime stamp={this.props.obj.last_stamp} + text={this.props.obj.last_retrieved} /> </dd> - <dt>Site link</dt> + </dl> + {this.getCore()} + <dl className="dl-horizontal"> + <form> + <dt>Filters</dt> <dd> - <a href={this.props.feed.site_link}> - {this.props.feed.site_link} - </a> + <button className="btn btn-default" + type="button" onClick={this.addFilterRow}> + <Glyphicon glyph='plus' /> + </button> + <button className="btn btn-default">Submit + </button> </dd> - <dt>Last fetched</dt> - <dd>{this.props.feed.last_retrieved}</dd> - <dt>Enabled</dt> - <dd>{this.props.feed.enabled}</dd> + {filter_rows} + </form> </dl> - </Panel> + </div> ); }, + addFilterRow: function() { + var filters = this.state.filters; + filters.push({action: null, action_on: null, + type: null, pattern: null}); + this.setState({filters: filters}); + }, + removeFilterRow: function(evnt) { + var filters = this.state.filters; + delete filters[evnt.target.getAttribute('data-index')]; + this.setState({filters: filters}); + }, }); var Category = React.createClass({ - propTypes: {category: React.PropTypes.object.isRequired}, - render: function() { - return (<Panel header={this.props.category.name}> - test - </Panel> - ); + mixins: [PanelMixin], + getInitialState: function() {return {edit_mode: false};}, + isEditable: function() { + if(this.props.obj.id != 0) {return true;} + else {return false;} + }, + isRemovable: function() {return this.isEditable();}, + fields: [{'title': 'Category name', 'type': 'string', 'key': 'name'}], + getTitle: function() {return this.props.obj.name;}, + getBody: function() { + return (<div className="panel-body"> + {this.getCore()} + </div>); }, }); @@ -112,18 +265,21 @@ var RightPanel = React.createClass({ </ol>); } if(this.state.current == 'article') { - var content = <Article article={this.state.article} />; + var cntnt = (<Article type='article' obj={this.state.article} + key={this.state.article.id} />); } else if(this.state.current == 'feed') { - var content = <Feed feed={this.state.feed} />; + var cntnt = (<Feed type='feed' obj={this.state.feed} + key={this.state.feed.id} />); } else if(this.state.current == 'category') { - var content = <Category category={this.state.category} />; + var cntnt = (<Category type='category' obj={this.state.category} + key={this.state.category.id} />); } return (<Col id="right-panel" xsOffset={2} smOffset={2} mdOffset={7} lgOffset={6} xs={10} sm={10} md={5} lg={6}> {breadcrum} - {content} + {cntnt} </Col> ); }, diff --git a/src/web/js/components/time.react.js b/src/web/js/components/time.react.js new file mode 100644 index 00000000..8b4d47d9 --- /dev/null +++ b/src/web/js/components/time.react.js @@ -0,0 +1,15 @@ +var React = require('react'); +var ReactIntl = require('react-intl'); + +var JarrTime = React.createClass({ + mixins: [ReactIntl.IntlMixin], + propTypes: {stamp: React.PropTypes.number.isRequired, + text: React.PropTypes.string.isRequired}, + render: function() { + return (<time dateTime={this.props.text} title={this.props.text}> + {this.formatRelative(this.props.stamp)} + </time>); + }, +}); + +module.exports = JarrTime; |