aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/conf.py2
-rw-r--r--src/web/controllers/article.py5
-rw-r--r--src/web/export.py2
-rw-r--r--src/web/js/actions/MiddlePanelActions.js1
-rw-r--r--src/web/js/components/MiddlePanel.react.js5
-rw-r--r--src/web/js/components/Navbar.react.js7
-rw-r--r--src/web/js/components/RightPanel.react.js3
-rw-r--r--src/web/templates/management.html6
-rw-r--r--src/web/views/article.py53
-rw-r--r--src/web/views/home.py3
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'])
bgstack15