diff options
-rw-r--r-- | src/web/js/actions/RightPanelActions.js | 17 | ||||
-rw-r--r-- | src/web/js/components/RightPanel.react.js | 156 | ||||
-rw-r--r-- | src/web/js/stores/RightPanelStore.js | 50 | ||||
-rw-r--r-- | src/web/views/api/feed.py | 1 |
4 files changed, 155 insertions, 69 deletions
diff --git a/src/web/js/actions/RightPanelActions.js b/src/web/js/actions/RightPanelActions.js index c60bffcf..838690d1 100644 --- a/src/web/js/actions/RightPanelActions.js +++ b/src/web/js/actions/RightPanelActions.js @@ -1,6 +1,7 @@ +var jquery = require('jquery'); var JarrDispatcher = require('../dispatcher/JarrDispatcher'); var ActionTypes = require('../constants/JarrConstants'); -var jquery = require('jquery'); +var MenuActions = require('../actions/MenuActions'); var RightPanelActions = { loadArticle: function(article_id, was_read_before) { @@ -13,7 +14,19 @@ var RightPanelActions = { }); } ); - + }, + _apiReq: function(meth, id, obj_type, data, success_callback) { + var args = {type: meth, contentType: 'application/json', + url: "api/v2.0/" + obj_type + "/" + id} + if(data) {args.data = JSON.stringify(data);} + if(success_callback) {args.success = success_callback;} + jquery.ajax(args); + }, + putObj: function(id, obj_type, fields) { + this._apiReq('PUT', id, obj_type, fields, MenuActions.reload); + }, + delObj: function(id, obj_type, fields) { + this._apiReq('DELETE', id, obj_type, null, MenuActions.reload); }, }; diff --git a/src/web/js/components/RightPanel.react.js b/src/web/js/components/RightPanel.react.js index 2e2aac3e..fda7c976 100644 --- a/src/web/js/components/RightPanel.react.js +++ b/src/web/js/components/RightPanel.react.js @@ -4,11 +4,15 @@ var Glyphicon = require('react-bootstrap/Glyphicon'); var Button = require('react-bootstrap/Button'); var ButtonGroup = require('react-bootstrap/ButtonGroup'); +var RightPanelActions = require('../actions/RightPanelActions'); var RightPanelStore = require('../stores/RightPanelStore'); var JarrTime = require('./time.react'); var PanelMixin = { propTypes: {obj: React.PropTypes.object.isRequired}, + getInitialState: function() { + return {edit_mode: false, obj: this.props.obj}; + }, getHeader: function() { var icon = null; if(this.props.obj.icon_url){ @@ -46,7 +50,9 @@ var PanelMixin = { var items = []; var key; if(!this.state.edit_mode) { - this.fields.map(function(field) { + this.fields.filter(function(field) { + return field.type != 'ignore'; + }).map(function(field) { key = this.getKey('dt', field.key); items.push(<dt key={key}>{field.title}</dt>); key = this.getKey('dd', field.key); @@ -67,24 +73,30 @@ var PanelMixin = { } }.bind(this)); } else { - this.fields.map(function(field) { + items.push(<dd key={this.getKey('dd', 'submit')}> + <button className="btn btn-default" + onClick={this.saveObj}> + Submit + </button> + </dd>); + this.fields.filter(function(field) { + return field.type != 'ignore'; + }).map(function(field) { key = this.getKey('dd', field.key); items.push(<dt key={key}>{field.title}</dt>); key = this.getKey('dt', field.key); + var input = null; if(field.type == 'string' || field.type == 'link') { - items.push(<dd key={key}><input type="text" - defaultValue={this.props.obj[field.key]} /> - </dd>); + input = (<input type="text" name={field.key} + onChange={this.saveField} + defaultValue={this.props.obj[field.key]} />); } else if (field.type == 'bool') { - items.push(<dd key={key}><input type="checkbox" - defaultChecked={this.props.obj[field.key]} /></dd>); + input = (<input type="checkbox" name={field.key} + onChange={this.saveField} + defaultChecked={this.props.obj[field.key]} />); } + items.push(<dd key={key}>{input}</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>); }, @@ -99,15 +111,39 @@ var PanelMixin = { this.setState({edit_mode: !this.state.edit_mode}); }, onClickRemove: function() { + RightPanelActions.delObj(this.props.obj.id, this.obj_type); + }, + saveField: function(evnt) { + var obj = this.state.obj; + for(var i in this.fields) { + if(evnt.target.name == this.fields[i].key) { + if(this.fields[i].type == 'bool') { + obj[evnt.target.name] = evnt.target.checked; + } else { + obj[evnt.target.name] = evnt.target.value; + } + break; + } + } + this.setState({obj: obj}); + }, + saveObj: function() { + var to_push = {}; + this.fields.map(function(field) { + to_push[field.key] = this.state.obj[field.key]; + }.bind(this)); + this.setState({edit_mode: false}, function() { + RightPanelActions.putObj(this.props.obj.id, this.obj_type, to_push); + }.bind(this)); }, }; var Article = React.createClass({ mixins: [PanelMixin], - getInitialState: function() {return {edit_mode: false};}, - fields: [], isEditable: function() {return false;}, isRemovable: function() {return true;}, + fields: [], + obj_type: 'article', getTitle: function() {return this.props.obj.title;}, getBody: function() { return (<div className="panel-body" dangerouslySetInnerHTML={ @@ -117,16 +153,15 @@ var Article = React.createClass({ var Feed = React.createClass({ mixins: [PanelMixin], - getInitialState: function() { - return {edit_mode: false, filters: this.props.obj.filters}; - }, isEditable: function() {return true;}, isRemovable: function() {return true;}, + obj_type: 'feed', 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'}, + {'title': 'Filters', 'type': 'ignore', 'key': 'filters'}, ], getTitle: function() {return this.props.obj.title;}, getFilterRow: function(i, filter) { @@ -138,30 +173,56 @@ var Feed = React.createClass({ <Glyphicon glyph='minus' /> </button> </span> + <select name="action on" className="form-control" + data-index={i} onChange={this.saveFilterChange} + defaultValue={filter['action on']}> + <option value="match">match</option> + <option value="no match">no match</option> + </select> + <input name="pattern" type="text" className="form-control" + data-index={i} onChange={this.saveFilterChange} + defaultValue={filter.pattern} /> <select name="type" className="form-control" + data-index={i} onChange={this.saveFilterChange} 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" + data-index={i} onChange={this.saveFilterChange} 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])); + getFilterRows: function() { + var rows = []; + if(this.state.edit_mode) { + for(var i in this.state.obj.filters) { + rows.push(this.getFilterRow(i, this.state.obj.filters[i])); + } + return (<dl className="dl-horizontal"> + <dt>Filters</dt> + <dd> + <button className="btn btn-default" + type="button" onClick={this.addFilterRow}> + <Glyphicon glyph='plus' /> + </button> + </dd> + {rows} + </dl>); } + rows = []; + rows.push(<dt key={'d-title'}>Filters</dt>); + for(var i in this.state.obj.filters) { + rows.push(<dd key={'d' + i}> + When {this.state.obj.filters[i]['action on']} on "{this.state.obj.filters[i].pattern}" ({this.state.obj.filters[i].type}) => {this.state.obj.filters[i].action} + </dd>); + } + return <dl className="dl-horizontal">{rows}</dl>; + }, + getBody: function() { return (<div className="panel-body"> <dl className="dl-horizontal"> <dt>Created on</dt> @@ -174,44 +235,37 @@ var Feed = React.createClass({ </dd> </dl> {this.getCore()} - <dl className="dl-horizontal"> - <form> - <dt>Filters</dt> - <dd> - <button className="btn btn-default" - type="button" onClick={this.addFilterRow}> - <Glyphicon glyph='plus' /> - </button> - <button className="btn btn-default">Submit - </button> - </dd> - {filter_rows} - </form> - </dl> + {this.getFilterRows()} </div> ); }, addFilterRow: function() { - var filters = this.state.filters; - filters.push({action: null, action_on: null, - type: null, pattern: null}); - this.setState({filters: filters}); + var obj = this.state.obj; + obj.filters.push({action: "mark as read", 'action on': "match", + type: "simple match", pattern: ""}); + this.setState({obj: obj}); }, removeFilterRow: function(evnt) { - var filters = this.state.filters; - delete filters[evnt.target.getAttribute('data-index')]; - this.setState({filters: filters}); + var obj = this.state.obj; + delete obj.filters[evnt.target.getAttribute('data-index')]; + this.setState({obj: obj}); + }, + saveFilterChange: function(evnt) { + var index = evnt.target.getAttribute('data-index'); + var obj = this.state.obj; + obj.filters[index][evnt.target.name] = evnt.target.value; + this.setState({obj: obj}); }, }); var Category = React.createClass({ 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();}, + obj_type: 'category', fields: [{'title': 'Category name', 'type': 'string', 'key': 'name'}], getTitle: function() {return this.props.obj.name;}, getBody: function() { @@ -299,7 +353,7 @@ var RightPanel = React.createClass({ RightPanelStore.removeChangeListener(this._onChange); }, _onChange: function() { - this.setState(RightPanelStore._datas); + this.setState(RightPanelStore.getAll()); }, }); diff --git a/src/web/js/stores/RightPanelStore.js b/src/web/js/stores/RightPanelStore.js index 85f336e4..6c268dfd 100644 --- a/src/web/js/stores/RightPanelStore.js +++ b/src/web/js/stores/RightPanelStore.js @@ -7,9 +7,13 @@ var MenuStore = require('../stores/MenuStore'); var RightPanelStore = assign({}, EventEmitter.prototype, { - _datas: {category: null, feed: null, article: null}, + category: null, + feed: null, + article: null, + current: null, getAll: function() { - return this._datas; + return {category: this.category, feed: this.feed, + article: this.article, current: this.current}; }, emitChange: function() { this.emit(CHANGE_EVENT); @@ -26,31 +30,45 @@ var RightPanelStore = assign({}, EventEmitter.prototype, { RightPanelStore.dispatchToken = JarrDispatcher.register(function(action) { switch(action.type) { case ActionTypes.PARENT_FILTER: - RightPanelStore._datas.article = null; + RightPanelStore.article = null; if(action.filter_id == null) { - RightPanelStore._datas.category = null; - RightPanelStore._datas.feed = null; - RightPanelStore._datas.current = null; + RightPanelStore.category = null; + RightPanelStore.feed = null; + RightPanelStore.current = null; } else if(action.filter_type == 'category_id') { - RightPanelStore._datas.category = MenuStore._datas.categories[action.filter_id]; - RightPanelStore._datas.feed = null; - RightPanelStore._datas.current = 'category'; + RightPanelStore.category = MenuStore._datas.categories[action.filter_id]; + RightPanelStore.feed = null; + RightPanelStore.current = 'category'; RightPanelStore.emitChange(); } else { - RightPanelStore._datas.feed = MenuStore._datas.feeds[action.filter_id]; - RightPanelStore._datas.category = MenuStore._datas.categories[RightPanelStore._datas.feed.category_id]; - RightPanelStore._datas.current = 'feed'; + RightPanelStore.feed = MenuStore._datas.feeds[action.filter_id]; + RightPanelStore.category = MenuStore._datas.categories[RightPanelStore.feed.category_id]; + RightPanelStore.current = 'feed'; RightPanelStore.emitChange(); } break; case ActionTypes.LOAD_ARTICLE: - RightPanelStore._datas.feed = MenuStore._datas.feeds[action.article.feed_id]; - RightPanelStore._datas.category = MenuStore._datas.categories[action.article.category_id]; - RightPanelStore._datas.article = action.article; - RightPanelStore._datas.current = 'article'; + RightPanelStore.feed = MenuStore._datas.feeds[action.article.feed_id]; + RightPanelStore.category = MenuStore._datas.categories[action.article.category_id]; + RightPanelStore.article = action.article; + RightPanelStore.current = 'article'; RightPanelStore.emitChange(); break; + case ActionTypes.RELOAD_MENU: + RightPanelStore.article = null; + if(RightPanelStore.category && !(RightPanelStore.category.id.toString() in action.categories)) { + RightPanelStore.category = null; + RightPanelStore.current = null; + } + if(RightPanelStore.feed && !(RightPanelStore.feed.id.toString() in action.feeds)) { + RightPanelStore.feed = null; + RightPanelStore.current = null; + } + if(RightPanelStore.current == 'article') { + RightPanelStore.current = null; + } + RightPanelStore.emitChange(); default: // pass } diff --git a/src/web/views/api/feed.py b/src/web/views/api/feed.py index dd9919bf..604620b4 100644 --- a/src/web/views/api/feed.py +++ b/src/web/views/api/feed.py @@ -22,6 +22,7 @@ FEED_ATTRS = {'title': {'type': str}, 'enabled': {'type': bool, 'default': True}, 'etag': {'type': str, 'default': ''}, 'icon_url': {'type': str, 'default': ''}, + 'filters': {'type': list}, 'last_modified': {'type': str}, 'last_retrieved': {'type': str}, 'last_error': {'type': str}, |