diff options
-rw-r--r-- | src/conf.py | 2 | ||||
-rw-r--r-- | src/web/controllers/article.py | 5 | ||||
-rw-r--r-- | src/web/export.py | 2 | ||||
-rw-r--r-- | src/web/js/actions/MiddlePanelActions.js | 1 | ||||
-rw-r--r-- | src/web/js/components/MiddlePanel.react.js | 5 | ||||
-rw-r--r-- | src/web/js/components/Navbar.react.js | 7 | ||||
-rw-r--r-- | src/web/js/components/RightPanel.react.js | 3 | ||||
-rw-r--r-- | src/web/templates/management.html | 6 | ||||
-rw-r--r-- | src/web/views/article.py | 53 | ||||
-rw-r--r-- | src/web/views/home.py | 3 |
10 files changed, 71 insertions, 16 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 += "<h1>List of feeds</h1>\n" diff --git a/src/web/js/actions/MiddlePanelActions.js b/src/web/js/actions/MiddlePanelActions.js index efae516a..3704e7ec 100644 --- a/src/web/js/actions/MiddlePanelActions.js +++ b/src/web/js/actions/MiddlePanelActions.js @@ -140,6 +140,7 @@ var MiddlePanelActions = { data: JSON.stringify(filters), url: "/mark_all_as_read", success: function (payload) { + console.log(payload); JarrDispatcher.dispatch({ type: ActionTypes.MARK_ALL_AS_READ, articles: payload.articles, diff --git a/src/web/js/components/MiddlePanel.react.js b/src/web/js/components/MiddlePanel.react.js index 561802aa..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 = <Glyphicon glyph="ban-circle" />; } var title = (<a href={'/article/redirect/' + this.props.article_id} - onClick={this.openRedirectLink} target="_blank"> + onClick={this.openRedirectLink} target="_blank" + title={this.props.feed_title}> {icon} {this.props.feed_title} </a>); var read = (<Glyphicon glyph={this.state.read?"check":"unchecked"} @@ -49,7 +50,7 @@ var TableLine = React.createClass({ } // FIXME https://github.com/yahoo/react-intl/issues/189 // use FormattedRelative when fixed, will have to upgrade to ReactIntlv2 - return (<div className={clsses} onClick={this.loadArticle}> + return (<div className={clsses} onClick={this.loadArticle} title={this.props.title}> <h5><strong>{title}</strong></h5> <JarrTime text={this.props.date} stamp={this.props.timestamp} /> diff --git a/src/web/js/components/Navbar.react.js b/src/web/js/components/Navbar.react.js index a5dedcf9..67e9ed56 100644 --- a/src/web/js/components/Navbar.react.js +++ b/src/web/js/components/Navbar.react.js @@ -38,13 +38,13 @@ JarrNavBar = React.createClass({ if(this.state.modalType == 'addFeed') { heading = 'Add a new feed'; action = '/feed/bookmarklet'; - placeholder = "Site or feed url, we'll sort it out later ;)"; + placeholder = "Site or feed url"; body = <Input name="url" type="text" placeholder={placeholder} />; } else { heading = 'Add a new category'; action = '/category/create'; body = <Input name="name" type="text" - placeholder="Name, there isn't much more to it" />; + placeholder="Name" />; } return (<Modal show={this.state.showModal} onHide={this.close}> <form action={action} method="POST"> @@ -105,6 +105,9 @@ JarrNavBar = React.createClass({ <MenuItem href="/user/profile"> <Glyphicon glyph="user" />Profile </MenuItem> + <MenuItem href="/user/management"> + <Glyphicon glyph="cog" />Your data + </MenuItem> <MenuItem href="/about"> <Glyphicon glyph="question-sign" />About </MenuItem> diff --git a/src/web/js/components/RightPanel.react.js b/src/web/js/components/RightPanel.react.js index f765607a..97a7c461 100644 --- a/src/web/js/components/RightPanel.react.js +++ b/src/web/js/components/RightPanel.react.js @@ -35,7 +35,6 @@ var PanelMixin = { </Button>); } btn_grp = (<ButtonGroup bsSize="small"> - {this.getExtraButton()} {edit_button} {rem_button} </ButtonGroup>); @@ -193,7 +192,6 @@ var Feed = React.createClass({ {'title': 'Category', 'type': 'ignore', 'key': 'category_id'}, ], getTitle: function() {return this.props.obj.title;}, - getExtraButton: function() {return null;}, getFilterRow: function(i, filter) { return (<dd key={'d' + i + '-' + this.props.obj.id} className="input-group filter-row"> @@ -353,7 +351,6 @@ var Category = React.createClass({ if(this.props.obj.id != 0) {return true;} else {return false;} }, - getExtraButton: function () {return null;}, isRemovable: function() {return this.isEditable();}, obj_type: 'category', fields: [{'title': 'Category name', 'type': 'string', 'key': 'name'}], diff --git a/src/web/templates/management.html b/src/web/templates/management.html index 01179b5e..dc95a052 100644 --- a/src/web/templates/management.html +++ b/src/web/templates/management.html @@ -14,18 +14,18 @@ <button class="btn btn-default" type="submit">OK</button> </form> <br /> - <a href="/export_opml" class="btn btn-default">{{ _('Export feeds to OPML') }}</a> + <a href="{{ url_for('articles.export', format='OPML') }}" class="btn btn-default">{{ _('Export feeds to OPML') }}</a> <h1>{{ _('Data liberation') }}</h1> <form action="" method="post" id="formImportJSON" enctype="multipart/form-data"> <span class="btn btn-default btn-file">{{ _('Import account') }} (<span class="text-info">*.json</span>)<input type="file" name="jsonfile" /></span> <button class="btn btn-default" type="submit">OK</button> </form> <br /> - <a href="/export?format=JSON" class="btn btn-default">{{ _('Export account to JSON') }}</a> + <a href="{{ url_for('articles.export', format='JSON') }}" class="btn btn-default">{{ _('Export account to JSON') }}</a> </div> <div class="well"> <h1>{{ _('Export articles') }}</h1> - <a href="/export?format=HTML" class="btn btn-default">HTML</a> + <a href="{{ url_for('articles.export', format='HTML') }}" class="btn btn-default">HTML</a> </div> </div><!-- /.container --> {% endblock %} diff --git a/src/web/views/article.py b/src/web/views/article.py index 46e8b786..6e565f36 100644 --- a/src/web/views/article.py +++ b/src/web/views/article.py @@ -1,11 +1,14 @@ from datetime import datetime, timedelta -from flask import (Blueprint, render_template, redirect, - flash, url_for, request) +from flask import (Blueprint, g, render_template, redirect, + 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') @@ -122,3 +125,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']) |