From 0e42e6d8ce1a056b4426148651b741f345968be2 Mon Sep 17 00:00:00 2001 From: Cédric Bonhomme Date: Wed, 6 Apr 2016 23:45:06 +0200 Subject: major problems fixed. --- src/conf.py | 2 +- src/web/controllers/article.py | 5 +++ src/web/export.py | 2 +- src/web/js/actions/MenuActions.js | 5 ++- src/web/js/actions/MiddlePanelActions.js | 6 ++-- src/web/js/components/MainApp.react.js | 2 +- src/web/js/components/Menu.react.js | 25 ++++++++++++--- src/web/js/components/MiddlePanel.react.js | 19 ++++++------ src/web/js/constants/JarrConstants.js | 22 ++++--------- src/web/js/stores/MenuStore.js | 25 ++++++++++++--- src/web/js/stores/MiddlePanelStore.js | 46 ++++++++++++--------------- src/web/templates/management.html | 6 ++-- src/web/views/article.py | 50 ++++++++++++++++++++++++++++-- src/web/views/home.py | 3 +- 14 files changed, 145 insertions(+), 73 deletions(-) diff --git a/src/conf.py b/src/conf.py index 9e7f1d13..f30b5701 100644 --- a/src/conf.py +++ b/src/conf.py @@ -47,7 +47,7 @@ DEFAULTS = {"platform_url": "https://jarr.herokuapp.com/", "host": "0.0.0.0", "port": "5000", "crawling_method": "classic", - "webzine_root": "/tmp", + "webzine_root": "~/tmp", } if not ON_HEROKU: diff --git a/src/web/controllers/article.py b/src/web/controllers/article.py index 8c6952cb..37a35023 100644 --- a/src/web/controllers/article.py +++ b/src/web/controllers/article.py @@ -96,3 +96,8 @@ class ArticleController(AbstractController): else: articles_counter[article.date.year] += 1 return articles_counter, articles + + def read_light(self, **filters): + return super().read(**filters).with_entities(Article.id, Article.title, + Article.readed, Article.like, Article.feed_id, Article.date, + Article.category_id).order_by(Article.date.desc()) diff --git a/src/web/export.py b/src/web/export.py index 220f8a42..41ee839c 100644 --- a/src/web/export.py +++ b/src/web/export.py @@ -139,7 +139,7 @@ def export_html(user): """ Export all articles of 'user' in Web pages. """ - webzine_root = conf.WEBZINE_ROOT + "webzine/" + webzine_root = conf.WEBZINE_ROOT + "/webzine/" nb_articles = format(len(models.Article.query.filter(models.Article.user_id == user.id).all()), ",d") index = HTML_HEADER("News archive") index += "

List of feeds

\n" diff --git a/src/web/js/actions/MenuActions.js b/src/web/js/actions/MenuActions.js index b9154581..824610d8 100644 --- a/src/web/js/actions/MenuActions.js +++ b/src/web/js/actions/MenuActions.js @@ -5,7 +5,7 @@ var jquery = require('jquery'); var MenuActions = { // PARENT FILTERS - reload: function() { + reload: function(setFilterFunc, id) { jquery.getJSON('/menu', function(payload) { JarrDispatcher.dispatch({ type: ActionTypes.RELOAD_MENU, @@ -18,6 +18,9 @@ var MenuActions = { crawling_method: payload.crawling_method, all_unread_count: payload.all_unread_count, }); + if(setFilterFunc && id) { + setFilterFunc(id); + } }); }, setFilter: function(filter) { diff --git a/src/web/js/actions/MiddlePanelActions.js b/src/web/js/actions/MiddlePanelActions.js index f805b7b1..3704e7ec 100644 --- a/src/web/js/actions/MiddlePanelActions.js +++ b/src/web/js/actions/MiddlePanelActions.js @@ -140,11 +140,9 @@ var MiddlePanelActions = { data: JSON.stringify(filters), url: "/mark_all_as_read", success: function (payload) { + console.log(payload); JarrDispatcher.dispatch({ - type: ActionTypes.CHANGE_ATTR, - attribute: 'read', - value_num: -1, - value_bool: true, + type: ActionTypes.MARK_ALL_AS_READ, articles: payload.articles, }); }, diff --git a/src/web/js/components/MainApp.react.js b/src/web/js/components/MainApp.react.js index cbdc5833..ffb14589 100644 --- a/src/web/js/components/MainApp.react.js +++ b/src/web/js/components/MainApp.react.js @@ -15,7 +15,7 @@ var MainApp = React.createClass({ + xs={12} sm={4} md={4} lg={4}> diff --git a/src/web/js/components/Menu.react.js b/src/web/js/components/Menu.react.js index 60578f8a..4537ee81 100644 --- a/src/web/js/components/Menu.react.js +++ b/src/web/js/components/Menu.react.js @@ -84,13 +84,15 @@ var CategoryGroup = React.createClass({ name: React.PropTypes.string.isRequired, feeds: React.PropTypes.array.isRequired, unread: React.PropTypes.number.isRequired, - folded: React.PropTypes.bool.isRequired, + folded: React.PropTypes.bool, }, getInitialState: function() { - return {folded: this.props.folded}; + return {folded: false}; }, componentWillReceiveProps: function(nextProps) { - this.setState({folded: nextProps.folded}); + if(nextProps.folded != null) { + this.setState({folded: nextProps.folded}); + } }, render: function() { // hidden the no category if empty @@ -265,7 +267,22 @@ var Menu = React.createClass({ ); }, componentDidMount: function() { - MenuActions.reload(); + var setFilterFunc = null; + var id = null; + if(window.location.search.substring(1)) { + var args = window.location.search.substring(1).split('&'); + args.map(function(arg) { + if (arg.split('=')[0] == 'at' && arg.split('=')[1] == 'c') { + setFilterFunc = MiddlePanelActions.setCategoryFilter; + } else if (arg.split('=')[0] == 'at' && arg.split('=')[1] == 'f') { + setFilterFunc = MiddlePanelActions.setFeedFilter; + + } else if (arg.split('=')[0] == 'ai') { + id = parseInt(arg.split('=')[1]); + } + }); + } + MenuActions.reload(setFilterFunc, id); MenuStore.addChangeListener(this._onChange); }, componentWillUnmount: function() { diff --git a/src/web/js/components/MiddlePanel.react.js b/src/web/js/components/MiddlePanel.react.js index dad33acc..f6e44777 100644 --- a/src/web/js/components/MiddlePanel.react.js +++ b/src/web/js/components/MiddlePanel.react.js @@ -35,7 +35,8 @@ var TableLine = React.createClass({ icon = ; } var title = ( + onClick={this.openRedirectLink} target="_blank" + title={this.props.feed_title}> {icon} {this.props.feed_title} ); var read = (); icon = ; - var newTab = ( - {icon} - ); var clsses = "list-group-item"; if(this.props.selected) { clsses += " active"; } // FIXME https://github.com/yahoo/react-intl/issues/189 // use FormattedRelative when fixed, will have to upgrade to ReactIntlv2 - return (
+ return (
{title}
-
{read} {liked} {newTab} {this.props.title}
+
{read} {liked} {this.props.title}
); }, @@ -81,7 +78,7 @@ var TableLine = React.createClass({ evnt.stopPropagation(); }, loadArticle: function() { - this.setState({active: true, read: true}, function() { + this.setState({selected: true, read: true}, function() { RightPanelActions.loadArticle( this.props.article_id, this.props.read); }.bind(this)); @@ -232,7 +229,11 @@ var MiddlePanel = React.createClass({ return (
{this.state.articles.map(function(article){ - return (OK
- {{ _('Export feeds to OPML') }} + {{ _('Export feeds to OPML') }}

{{ _('Data liberation') }}

{{ _('Import account') }} (*.json)

- {{ _('Export account to JSON') }} + {{ _('Export account to JSON') }}

{{ _('Export articles') }}

- HTML + HTML
{% endblock %} diff --git a/src/web/views/article.py b/src/web/views/article.py index 416bb96c..94f661fa 100644 --- a/src/web/views/article.py +++ b/src/web/views/article.py @@ -2,12 +2,14 @@ # -*- coding: utf-8 - from datetime import datetime, timedelta from flask import (Blueprint, g, render_template, redirect, - flash, url_for, request) + flash, url_for, make_response, request) from flask.ext.babel import gettext from flask.ext.login import login_required, current_user +from web.export import export_json, export_html from web.lib.utils import clear_string, redirect_url -from web.controllers import ArticleController +from web.controllers import (ArticleController, UserController, + CategoryController) from web.lib.view_utils import etag_match articles_bp = Blueprint('articles', __name__, url_prefix='/articles') @@ -124,3 +126,47 @@ def expire(): query.delete() flash(gettext('%(count)d articles deleted', count=count), 'info') return redirect(redirect_url()) + + +@articles_bp.route('/export', methods=['GET']) +@login_required +def export(): + """ + Export all articles to HTML or JSON. + """ + user = UserController(current_user.id).get(id=current_user.id) + if request.args.get('format') == "HTML": + # Export to HTML + try: + archive_file, archive_file_name = export_html(user) + except Exception as e: + print(e) + flash(gettext("Error when exporting articles."), 'danger') + return redirect(redirect_url()) + response = make_response(archive_file) + response.headers['Content-Type'] = 'application/x-compressed' + response.headers['Content-Disposition'] = 'attachment; filename=%s' \ + % archive_file_name + elif request.args.get('format') == "JSON": + # Export to JSON + try: + json_result = export_json(user) + except Exception as e: + flash(gettext("Error when exporting articles."), 'danger') + return redirect(redirect_url()) + response = make_response(json_result) + response.mimetype = 'application/json' + response.headers["Content-Disposition"] \ + = 'attachment; filename=account.json' + elif request.args.get('format') == "OPML": + categories = {cat.id: cat.dump() + for cat in CategoryController(user.id).read()} + response = make_response(render_template('opml.xml', user=user, + categories=categories, + now=datetime.now())) + response.headers['Content-Type'] = 'application/xml' + response.headers['Content-Disposition'] = 'attachment; filename=feeds.opml' + else: + flash(gettext('Export format not supported.'), 'warning') + return redirect(redirect_url()) + return response diff --git a/src/web/views/home.py b/src/web/views/home.py index fd677b3c..12a06024 100644 --- a/src/web/views/home.py +++ b/src/web/views/home.py @@ -133,8 +133,9 @@ def get_article(article_id, parse=False): def mark_all_as_read(): filters = _get_filters(request.json) acontr = ArticleController(current_user.id) + processed_articles = _articles_to_json(acontr.read_light(**filters)) acontr.update(filters, {'readed': True}) - return _articles_to_json(acontr.read(**filters)) + return processed_articles @current_app.route('/fetch', methods=['GET']) -- cgit