aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/web/js/actions/RightPanelActions.js17
-rw-r--r--src/web/js/components/RightPanel.react.js156
-rw-r--r--src/web/js/stores/RightPanelStore.js50
-rw-r--r--src/web/views/api/feed.py1
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},
bgstack15