summaryrefslogtreecommitdiff
path: root/talermerchantdemos
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-10-10 22:55:31 +0200
committerChristian Grothoff <christian@grothoff.org>2020-10-10 22:55:31 +0200
commit2e665813a44988bfd906c0fab773f82652047841 (patch)
treeabb09bb226ec904d23592edbc8372634f5b7a31b /talermerchantdemos
parentad45c1d0d6152d6eddde39881461a3472f799ab7 (diff)
parent58af97724593e6d9cf423035f46da8f88c29526c (diff)
downloadtaler-merchant-demos-2e665813a44988bfd906c0fab773f82652047841.tar.gz
taler-merchant-demos-2e665813a44988bfd906c0fab773f82652047841.tar.bz2
taler-merchant-demos-2e665813a44988bfd906c0fab773f82652047841.zip
merge torsten-redesign branch, implement i18n support
Diffstat (limited to 'talermerchantdemos')
-rw-r--r--talermerchantdemos/.gitignore6
-rw-r--r--talermerchantdemos/.vscode/launch.json15
-rw-r--r--talermerchantdemos/blog/articles/scrap1_11.html1
-rw-r--r--talermerchantdemos/blog/blog.py139
-rw-r--r--talermerchantdemos/blog/content.py13
-rw-r--r--talermerchantdemos/blog/templates/article_frame.html13
-rw-r--r--talermerchantdemos/blog/templates/article_frame.html.j215
-rw-r--r--talermerchantdemos/blog/templates/article_refunded.html10
-rw-r--r--talermerchantdemos/blog/templates/article_refunded.html.j216
-rw-r--r--talermerchantdemos/blog/templates/base.html79
-rw-r--r--talermerchantdemos/blog/templates/base.html.j2119
-rw-r--r--talermerchantdemos/blog/templates/confirm_refund.html19
-rw-r--r--talermerchantdemos/blog/templates/confirm_refund.html.j222
-rw-r--r--talermerchantdemos/blog/templates/error.html22
-rw-r--r--talermerchantdemos/blog/templates/error.html.j224
-rw-r--r--talermerchantdemos/blog/templates/index.html40
-rw-r--r--talermerchantdemos/blog/templates/show_refund.html28
-rw-r--r--talermerchantdemos/static/__init__.py1
-rw-r--r--talermerchantdemos/static/demo.scss (renamed from talermerchantdemos/static/demo.css)27
-rw-r--r--talermerchantdemos/static/navbar.css75
-rw-r--r--talermerchantdemos/static/navbar.css.map1
-rw-r--r--talermerchantdemos/static/navbar.scss103
-rw-r--r--talermerchantdemos/static/pure.scss (renamed from talermerchantdemos/static/pure.css)2
-rw-r--r--talermerchantdemos/static/taler-fallback.scss (renamed from talermerchantdemos/static/taler-fallback.css)0
24 files changed, 535 insertions, 255 deletions
diff --git a/talermerchantdemos/.gitignore b/talermerchantdemos/.gitignore
new file mode 100644
index 0000000..c940928
--- /dev/null
+++ b/talermerchantdemos/.gitignore
@@ -0,0 +1,6 @@
+*.pyc
+*/__pycache__
+
+./static/*.css
+
+*/*.css.map \ No newline at end of file
diff --git a/talermerchantdemos/.vscode/launch.json b/talermerchantdemos/.vscode/launch.json
new file mode 100644
index 0000000..7a9dfa0
--- /dev/null
+++ b/talermerchantdemos/.vscode/launch.json
@@ -0,0 +1,15 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "pwa-chrome",
+ "request": "launch",
+ "name": "Launch Chrome against localhost",
+ "url": "http://localhost:8080",
+ "webRoot": "${workspaceFolder}"
+ }
+ ]
+} \ No newline at end of file
diff --git a/talermerchantdemos/blog/articles/scrap1_11.html b/talermerchantdemos/blog/articles/scrap1_11.html
index 71c693b..17471df 100644
--- a/talermerchantdemos/blog/articles/scrap1_11.html
+++ b/talermerchantdemos/blog/articles/scrap1_11.html
@@ -52,4 +52,3 @@ traditional style, please visit
<br/>
</p>
<img alt="song-book-jutta-scrunch-crop" src="/essay/11._The_Free_Software_Song/data/song-book-jutta-scrunch-crop.jpg"/>
-
diff --git a/talermerchantdemos/blog/blog.py b/talermerchantdemos/blog/blog.py
index d6cd3fc..c8315e5 100644
--- a/talermerchantdemos/blog/blog.py
+++ b/talermerchantdemos/blog/blog.py
@@ -1,6 +1,6 @@
##
-# This file is part of GNU taler.
-# Copyright (C) 2014-2017 INRIA
+# This file is part of GNU Taler.
+# Copyright (C) 2014-2020 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
@@ -24,6 +24,11 @@ import traceback
import uuid
import base64
import flask
+from flask import request
+from flask_babel import Babel
+from flask_babel import refresh
+from flask_babel import force_locale
+from flask_babel import gettext
import time
import sys
from urllib.parse import urljoin, urlencode, urlparse
@@ -85,6 +90,34 @@ ARTICLE_AMOUNT = CURRENCY + ":0.5"
BACKEND_URL = urljoin(BACKEND_BASE_URL, "instances/blog/")
app.config.from_object(__name__)
+babel = Babel(app)
+
+print("Using translations from:")
+print(list(babel.translation_directories))
+translations = [str(translation) for translation in babel.list_translations()]
+translations.append('en')
+print("Operating with the following translations available:")
+print(translations)
+
+
+##
+# Helper function used inside Jinja2 logic to create a links
+# to the current page but in a different language. Used to
+# implement the "Language" menu.
+#
+def self_localized(lang):
+ """
+ Return URL for the current page in another locale.
+ """
+ path = request.path
+ # path must have the form "/$LANG/$STUFF"
+ parts = path.split('/', 2)
+ if (2 >= len(parts)):
+ # Totally unexpected path format, do not localize
+ return path
+ return "/" + lang + "/" + parts[2]
+
+app.jinja_env.globals.update(self_localized=self_localized)
##
@@ -107,7 +140,7 @@ def utility_processor():
# @param abort_status_code status code to return along the response.
# @param params _kw_ arguments to passed verbatim to the templating engine.
def err_abort(abort_status_code, **params):
- t = flask.render_template("templates/error.html", **params)
+ t = flask.render_template("templates/error.html.j2", **params)
flask.abort(flask.make_response(t, abort_status_code))
@@ -120,23 +153,44 @@ def err_abort(abort_status_code, **params):
@app.errorhandler(Exception)
def internal_error(e):
return flask.render_template(
- "templates/error.html", message="Internal error", stack=traceback.format_exc()
+ "templates/error.html.j2", message=gettext("Internal error"), stack=traceback.format_exc()
)
##
-# Serve the main index page.
+# Serve the main index page, redirecting to /<lang>/
#
# @return response object of the index page.
@app.route("/")
def index():
+ default = 'en'
+ target = flask.request.accept_languages.best_match(translations, default)
+ return flask.redirect("/" + target + "/", code=302)
+
+@babel.localeselector
+def get_locale():
+ parts = request.path.split('/', 2)
+ if (2 >= len(parts)):
+ # Totally unexpected path format, do not localize
+ return "en"
+ lang = parts[1]
+ if lang in translations:
+ return lang
+ return "en"
+
+##
+# Serve the main index page for a particular language.
+#
+# @return response object of the index page.
+@app.route("/<lang>/")
+def start(lang):
return flask.render_template(
- "templates/index.html", merchant_currency=CURRENCY, articles=ARTICLES.values()
+ "templates/index.html.j2", lang=lang, merchant_currency=CURRENCY, articles=ARTICLES.values()
)
-@app.route("/confirm-refund/<order_id>", methods=["GET"])
-def confirm_refund(order_id):
+@app.route("/<lang>/confirm-refund/<order_id>", methods=["GET"])
+def confirm_refund(lang, order_id):
session_id = flask.session.get("session_id", "")
pay_status = backend_get(
BACKEND_URL, f"private/orders/{order_id}", params=dict(session_id=session_id)
@@ -144,16 +198,16 @@ def confirm_refund(order_id):
order_status = pay_status.get("order_status")
if order_status != "paid":
err_abort(
- 400, message="can't refund unpaid article",
+ 400, message="Cannot refund unpaid article",
)
article_name = pay_status["contract_terms"]["extra"]["article_name"]
if not refundable(pay_status):
return flask.render_template(
- "templates/error.html", message="Item not refundable (anymore)"
+ "templates/error.html.j2", message=gettext("Article is not anymore refundable")
)
return flask.render_template(
- "templates/confirm_refund.html", article_name=article_name, order_id=order_id
+ "templates/confirm_refund.html.j2", article_name=article_name, order_id=order_id
)
@@ -163,16 +217,15 @@ def confirm_refund(order_id):
#
# @param order_id the order ID of the transaction to refund.
# @return the following errors (named by HTTP response code):
-# - 400: no article was asked to be refunded!
-# - 401: the refund was asked on a non-payed article.
-# - 500: the backend was unable to give response.
-# Or, in the successful case, a redirection to the
-# "refund URL" is returned; then the wallet will run
-# the refund protocol in a transparent way.
+# - 400: order unknown
+# - 402: the refund was asked on an unpaid article.
+# - 302: in the successful case, a redirection to the
+# "refund URL" is returned; then the wallet will run
+# the refund protocol in a transparent way.
@app.route("/refund/<order_id>", methods=["POST"])
-def refund(order_id):
+def refund(lang, order_id):
if not order_id:
- return flask.jsonify(dict(error="Aborting refund: article not payed")), 401
+ return flask.jsonify(dict(error="Aborting refund: order unknown")), 400
session_id = flask.session.get("session_id", "")
pay_status = backend_get(
BACKEND_URL, f"private/orders/{order_id}", params=dict(session_id=session_id)
@@ -219,7 +272,7 @@ def render_article(article_name, data, order_id, refundable):
err_abort(404, message=m)
# the order_id is needed for refunds
return flask.render_template(
- "templates/article_frame.html",
+ "templates/article_frame.html.j2",
article_file=get_article_file(article_info),
article_name=article_name,
order_id=order_id,
@@ -247,6 +300,25 @@ def post_order(article_name,lang):
dict(order=order, refund_delay=dict(d_ms=1000 * 120)))
return order_resp
+##
+# Setup a fresh order with the backend.
+#
+# @param article_name which article the order is for
+# @param lang which language to use
+#
+def post_order(article_name,lang):
+ order = dict(
+ amount=ARTICLE_AMOUNT,
+ extra=dict(article_name=article_name,lang=lang),
+ fulfillment_url=flask.request.base_url,
+ summary="Essay: " + article_name.replace("_", " "),
+ # 10 minutes time for a refund
+ refund_deadline=dict(t_ms=1000 * int(time.time() + 10 * 30)),
+ wire_transfer_deadline=dict(t_ms=1000 * int(time.time() + 15 * 30)),
+ )
+ order_resp = backend_post(BACKEND_URL, "private/orders", dict(order=order))
+ return order_resp
+
##
# Trigger a article purchase. The logic follows the main steps:
@@ -267,9 +339,10 @@ def post_order(article_name,lang):
# In the successful case, either the article is returned, or
# the browser gets redirected to a page where the wallet can
# send the payment.
-@app.route("/essay/<article_name>")
+@app.route("/<lang>/essay/<article_name>")
+@app.route("/<lang>/essay/<article_name>/data/<data>")
@app.route("/essay/<article_name>/data/<data>")
-def article(article_name, data=None):
+def article(article_name, lang=None, data=None):
# We use an explicit session ID so that each payment (or payment replay) is
# bound to a browser. This forces re-play and prevents sharing the article
# by just sharing the URL.
@@ -279,8 +352,6 @@ def article(article_name, data=None):
if not session_id:
session_id = flask.session["session_id"] = str(uuid.uuid4())
order_id = None
- # Temporary workaround...
- lang="en"
##
# First-timer; generate order first.
if not order_id:
@@ -309,12 +380,19 @@ def article(article_name, data=None):
if order_status == "paid":
refunded = pay_status["refunded"]
if refunded:
- return flask.render_template(
- "templates/article_refunded.html",
+ return flask.render_template(
+ "templates/article_refunded.html.j2",
article_name=article_name,
order_id=order_id,
)
- return render_article(article_name, data, order_id, refundable(pay_status))
+ response = render_article(article_name, data, order_id, refundable(pay_status))
+ response.set_cookie(
+ "order_id", order_id, path=urllib.parse.quote(f"/essay/{article_name}")
+ )
+ response.set_cookie(
+ "order_id", order_id, path=urllib.parse.quote(f"/{lang}/essay/{article_name}")
+ )
+ return response
# Check if the customer came on this page via the
# re-purchase detection mechanism
@@ -333,14 +411,17 @@ def article(article_name, data=None):
response.set_cookie(
"order_id", order_id, path=urllib.parse.quote(f"/essay/{article_name}")
)
+ response.set_cookie(
+ "order_id", order_id, path=urllib.parse.quote(f"/{lang}/essay/{article_name}")
+ )
return response
@app.errorhandler(500)
def handler(e):
return flask.render_template(
- "templates/error.html", message="Internal server error")
+ "templates/error.html.j2", message=gettext("Internal server error"))
@app.errorhandler(404)
def handler(e):
return flask.render_template(
- "templates/error.html", message="Page not found")
+ "templates/error.html.j2", message=gettext("Page not found"))
diff --git a/talermerchantdemos/blog/content.py b/talermerchantdemos/blog/content.py
index a0e90dd..fa9ace2 100644
--- a/talermerchantdemos/blog/content.py
+++ b/talermerchantdemos/blog/content.py
@@ -26,7 +26,7 @@ from pkg_resources import resource_stream, resource_filename
LOGGER = logging.getLogger(__name__)
NOISY_LOGGER = logging.getLogger("chardet.charsetprober")
NOISY_LOGGER.setLevel(logging.INFO)
-Article = namedtuple("Article", "slug title teaser main_file extra_files")
+Article = namedtuple("Article", "slug title teaser main_file extra_files lang")
##
# @var if a article is added to this list, then it will
@@ -43,8 +43,9 @@ ARTICLES = OrderedDict()
# @param main_file path to the article's HTML file.
# @param extra_file collection of extra files associated with the
# article, like images and sounds.
-def add_article(slug, title, teaser, main_file, extra_files):
- ARTICLES[slug] = Article(slug, title, teaser, main_file, extra_files)
+# @param lang language of the arcile
+def add_article(slug, title, teaser, main_file, extra_files, lang='en'):
+ ARTICLES[slug] = Article(slug, title, teaser, main_file, extra_files, lang)
##
@@ -85,7 +86,7 @@ def add_from_html(resource_name, teaser_paragraph=0, title=None):
if title is None:
title_el = soup.find("h1", attrs={"class": ["chapter", "unnumbered"]})
if title_el is None:
- LOGGER.warning("Can't extract title from '%s'", resource_name)
+ LOGGER.warning("Cannot extract title from '%s'", resource_name)
title = resource_name
else:
title = title_el.get_text().strip()
@@ -97,7 +98,7 @@ def add_from_html(resource_name, teaser_paragraph=0, title=None):
teaser = paragraphs[teaser_paragraph].get_text()
else:
teaser = teaser.get_text()
- re_proc = re.compile("^/essay/[^/]+/data/[^/]+$")
+ re_proc = re.compile("^/[^/][^/]/essay/[^/]+/data/[^/]+$")
imgs = soup.find_all("img")
extra_files = []
for img in imgs:
@@ -114,7 +115,7 @@ def add_from_html(resource_name, teaser_paragraph=0, title=None):
else:
LOGGER.warning("Image src and slug don't match: '%s' != '%s'" \
% (img['src'].split(os.sep)[2], slug))
- add_article(slug, title, teaser, resource_name, extra_files)
+ add_article(slug, title, teaser, resource_name, extra_files, 'en')
add_from_html("blog/articles/scrap1_U.0.html", 0)
diff --git a/talermerchantdemos/blog/templates/article_frame.html b/talermerchantdemos/blog/templates/article_frame.html
deleted file mode 100644
index a5050d3..0000000
--- a/talermerchantdemos/blog/templates/article_frame.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends "templates/base.html" %}
-{% block main %}
-{% include "articles/" + article_file %}
-
-{% if refundable %}
-<hr>
-<p>
- You don't like this article? <a href="{{ url_for('confirm_refund', order_id=order_id) }}">Get a refund</a> within
- the first hour after buying it.
-</p>
-{% endif %}
-
-{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/article_frame.html.j2 b/talermerchantdemos/blog/templates/article_frame.html.j2
new file mode 100644
index 0000000..a878e95
--- /dev/null
+++ b/talermerchantdemos/blog/templates/article_frame.html.j2
@@ -0,0 +1,15 @@
+{% extends "templates/base.html.j2" %}
+{% block main %}
+{% include "articles/" + article_file %}
+
+{% if refundable %}
+<hr>
+<p>
+ {{
+ gettext("You did not like this article?") +
+ gettext("You can <a href="{url}">request a refund</a> within the first hour after buying it.").format(url=url_for('confirm_refund', lang='en', order_id=order_id)
+ }}
+</p>
+{% endif %}
+
+{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/article_refunded.html b/talermerchantdemos/blog/templates/article_refunded.html
deleted file mode 100644
index 95c4a6b..0000000
--- a/talermerchantdemos/blog/templates/article_refunded.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends "templates/base.html" %}
-{% block main %}
-
-<h2>Refunded</h2>
-
-<p>Your payment (order ID <tt>{{ order_id }}<tt>) for the article "{{ article_name }}" has been refunded.</p>
-
-<p>You won't be able to view it until you pay for it again.</p>
-
-{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/article_refunded.html.j2 b/talermerchantdemos/blog/templates/article_refunded.html.j2
new file mode 100644
index 0000000..34e0a0b
--- /dev/null
+++ b/talermerchantdemos/blog/templates/article_refunded.html.j2
@@ -0,0 +1,16 @@
+{% extends "templates/base.html.j2" %}
+{% block main %}
+
+<h2>{{ gettext("Refunded") }}</h2>
+
+<p>
+{{
+ gettext("Your payment (order ID <tt>{order}<tt>) for the article "{article}" has been refunded.").format(order=order_id,article=article_name)
+}}
+</p>
+
+<p>
+{{ gettext("You won't be able to view it until you pay for it again.") }}
+</p>
+
+{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/base.html b/talermerchantdemos/blog/templates/base.html
deleted file mode 100644
index 3d7d0f0..0000000
--- a/talermerchantdemos/blog/templates/base.html
+++ /dev/null
@@ -1,79 +0,0 @@
-<!DOCTYPE html>
-<!--
- This file is part of GNU TALER.
- Copyright (C) 2014, 2015, 2016 INRIA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2.1, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--->
-
-<html>
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- {% block meta %}{% endblock %}
- <title>Taler Essay Shop Demo</title>
- <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='pure.css') }}" />
- <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='demo.css') }}" />
- <style>
- .warn {
- background-color: #aa393977;
- padding: 1em;
- }
- .notice {
- border-radius: 1em;
- background: #0333;
- border-left: 0.3em solid #033;
- padding-left: 1em;
- padding-top: 0.5em;
- padding-bottom: 0.5em;
- margin-top: 2em;
- margin-bottom: 2em;
- }
- #main a:link, #main a:visited, #main a:hover, #main a:active {
- color: black;
- }
- </style>
- {% block styles %}{% endblock %}
- {% block scripts %}{% endblock %}
-</head>
-
-<body>
- <div class="demobar" style="display: flex; flex-direction: column;">
- <h1><span class="tt adorn-brackets">Taler Demo</span></h1>
- <h1><span class="it"><a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG') }}">Shop</a></span></h1>
- <p>On this page you can buy articles using an imaginary currency (for now).
- The articles are chapters from Richard Stallman's book &quot;Free Software, Free Society&quot;,
- which is also
- <a href="http://shop.fsf.org/product/free-software-free-society-2/">published by the FSF</a>
- and available gratis at <a href="http://www.gnu.org/">gnu.org</a>.
- </p>
- <ul>
- <li><a href="{{ env('TALER_ENV_URL_INTRO', '#') }}">Introduction</a></li>
- <li><a href="{{ env('TALER_ENV_URL_BANK', '#') }}">Bank</a></li>
- <li><a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG', '#') }}">Essay Shop</a></li>
- <li><a href="{{ env('TALER_ENV_URL_MERCHANT_DONATIONS', '#') }}">Donations</a></li>
- <li><a href="{{ env('TALER_ENV_URL_MERCHANT_SURVEY', '#') }}">Tipping/Survey</a></li>
- <li><a href="{{ env('TALER_ENV_URL_BACKOFFICE', '#') }}">Back-office</a></li>
- </ul>
- <p>You can learn more about Taler on our main <a href="https://taler.net">website</a>.</p>
- <div style="flex-grow:1"></div>
- <p>Copyright &copy; 2014&mdash;2018 Inria</p>
- </div>
-
- <section id="main" class="content">
- {% block main %}
- This is the main content of the page.
- {% endblock %}
- <hr />
- </section>
-</body>
-</html>
diff --git a/talermerchantdemos/blog/templates/base.html.j2 b/talermerchantdemos/blog/templates/base.html.j2
new file mode 100644
index 0000000..58ce857
--- /dev/null
+++ b/talermerchantdemos/blog/templates/base.html.j2
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!--
+ This file is part of GNU TALER.
+ Copyright (C) 2014, 2015, 2016, 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+-->
+
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ {% block meta %}{% endblock %}
+ <title>{{ gettext("Taler Essay Shop Demo") }}</title>
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='pure.css') }}" />
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='demo.css') }}" />
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='navbar.css') }}" />
+ <style>
+ .warn {
+ background-color: #aa393977;
+ padding: 1em;
+ }
+ @keyframes hoveranim {
+ from {left:0;}
+ to {left:1vw;}
+ }
+ @keyframes hoveranimrevert {
+ from {left:1vw;}
+ to {left:0;}
+ }
+ .notice {
+ border-radius: 1em;
+ background: #0333;
+ border-left: 0.3em solid #033;
+ padding-left: 1em;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ margin-top: 2em;
+ margin-bottom: 2em;
+ }
+ .notice {
+ position: relative;
+ left: 0;
+ animation-name: hoveranimrevert;
+ animation-duration: 1s;
+ }
+ .notice:hover {
+ left: 1vw;
+ animation-name: hoveranim;
+ animation-duration: 1s;
+ }
+ #main a:link, #main a:visited, #main a:hover, #main a:active {
+ color: black;
+ }
+ </style>
+
+ {% block styles %}{% endblock %}
+ {% block scripts %}{% endblock %}
+</head>
+
+<body>
+ <header class="demobar" style="display: flex; flex-direction: column;">
+ <h1><span class="tt adorn-brackets">Taler Demo</span></h1>
+ <h1><span class="it"><a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG') }}">Shop</a></span></h1>
+ <p>{{
+ gettext("On this page you can buy articles using an imaginary currency.") + "<br>" +
+ gettext("The articles are chapters from Richard Stallman's book &quot;Free Software, Free Society&quot;.") + "<br>" +
+ gettext('The book is <a href="{shop}">published by the FSF</a> and available gratis at <a href="{gnu}">gnu.org</a>.').format(shop="https://shop.fsf.org/product/free-software-free-society-2", gnu="https://www.gnu.org")
+ }}
+ </p>
+ </header>
+ <div style="display:flex; flex-direction: column;" class="navcontainer">
+ <nav class="demolist">
+ <a href="{{ env('TALER_ENV_URL_INTRO', '#') }}">{{gettext("Introduction")}}</a>
+ <a href="{{ env('TALER_ENV_URL_BANK', '#') }}">{{gettext("Bank")}}</a>
+ <a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG', '#') }}" class="active">{{gettext("Essay Shop")}}</a>
+ <a href="{{ env('TALER_ENV_URL_MERCHANT_DONATIONS', '#') }}">{{gettext("Donations")}}</a>
+ <a href="{{ env('TALER_ENV_URL_MERCHANT_SURVEY', '#') }}">{{gettext("Tipping/Survey")}}</a>
+ <!-- a href="{{ env('TALER_ENV_URL_BACKOFFICE', '#') }}">{{gettext("Back-office")}}</a -->
+ <span class="right">
+ {{ gettext("English [en]") }}
+ <!-- <input type="checkbox"> -->
+ <div class="nav">
+ <br>
+ <!--<hr style="width: 100%;">-->
+ {% if lang != 'en' %}
+ <a href="{{ self_localized('en') }}" class="navbtn">English [en]</a><br>
+ {% endif %}
+ {% if lang != 'de' %}
+ <a href="{{ self_localized('de') }}" class="navbtn">Deutsch [de]</a><br>
+ {% endif %}
+ </div>
+ </span>
+ </nav>
+ </div>
+ <!-- <input type="checkbox" class="r"><label>test</label> -->
+
+ <section id="main" class="content">
+ {% block main %}
+ This is the main content of the page.
+ {% endblock %}
+ <hr />
+ <div>
+ <p>{{ gettext('You can learn more about Taler on our main <a href="{site}">website</a>.').format(site="https://taler.net/") }}</p>
+ <div style="flex-grow:1"></div>
+ <p>Copyright &copy; 2014&mdash;2020 Taler Systems SA</p>
+ </div>
+ </section>
+</body>
+</html>
diff --git a/talermerchantdemos/blog/templates/confirm_refund.html b/talermerchantdemos/blog/templates/confirm_refund.html
deleted file mode 100644
index 10aaa74..0000000
--- a/talermerchantdemos/blog/templates/confirm_refund.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% extends "templates/base.html" %}
-{% block main %}
- <h1>Refund Article?</h1>
-
- <p>
- Do you want to get a refund for the article <em>{{ article_name }}</em>? After you've requested a refund,
- you won't be able to read the article anymore.
- </p>
-
- <p>
- You will only be able to receive the refund on the same wallet that you've used to pay
- for this article originally.
- </p>
-
- <form action="{{ url_for('refund', order_id=order_id) }}" method="POST">
- <input type="text" name="article_name" value={{ article_name}} hidden>
- <input type="submit" value="Request refund">
- </form>
-{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/confirm_refund.html.j2 b/talermerchantdemos/blog/templates/confirm_refund.html.j2
new file mode 100644
index 0000000..09f3730
--- /dev/null
+++ b/talermerchantdemos/blog/templates/confirm_refund.html.j2
@@ -0,0 +1,22 @@
+{% extends "templates/base.html.j2" %}
+{% block main %}
+ <h1>{{ gettext("Confirm refund request for article"))</h1>
+
+ <p>
+ {{
+ gettext("Do you want to get a refund for the article <em>{name}</em>?").format(name=article_name) +
+ gettext("After you have requested a refund, you won't be able to read the article anymore.")
+ }}
+ </p>
+
+ <p>
+ {{
+ gettext ("You will only be able to receive the refund on the same wallet that you have used to pay for this article originally.")
+ }}
+ </p>
+
+ <form action="{{ url_for('refund', order_id=order_id) }}" method="POST">
+ <input type="text" name="article_name" value={{ article_name}} hidden>
+ <input type="submit" value="Request refund">
+ </form>
+{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/error.html b/talermerchantdemos/blog/templates/error.html
deleted file mode 100644
index 0d4bd02..0000000
--- a/talermerchantdemos/blog/templates/error.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "templates/base.html" %}
-{% block main %}
- <h1>An Error Occurred</h1>
-
- <p>{{ message }}</p>
-
- {% if status_code %}
- <p>The backend returned status code {{ status_code }}.</p>
- {% endif %}
-
- {% if json %}
- <p>Backend Response:</p>
- <pre>{{ json }}</pre>
- {% endif %}
-
- {% if stack %}
- <p>Stack trace:</p>
- <pre>
- {{ stack }}
- </pre>
- {% endif %}
-{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/error.html.j2 b/talermerchantdemos/blog/templates/error.html.j2
new file mode 100644
index 0000000..ffc2e1f
--- /dev/null
+++ b/talermerchantdemos/blog/templates/error.html.j2
@@ -0,0 +1,24 @@
+{% extends "templates/base.html.j2" %}
+{% block main %}
+ <h1>{{ gettext("Error encountered") }}</h1>
+
+ <p>{{ message }}</p>
+
+ {% if status_code %}
+ <p>
+ {{ gettext ("The backend returned status code {code}.").format(code=status_code) }}.
+ </p>
+ {% endif %}
+
+ {% if json %}
+ <p>{{gettext("Backend response:")}}</p>
+ <pre>{{ json }}</pre>
+ {% endif %}
+
+ {% if stack %}
+ <p>{{gettext("Stack trace:")}}</p>
+ <pre>
+ {{ stack }}
+ </pre>
+ {% endif %}
+{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/index.html b/talermerchantdemos/blog/templates/index.html
deleted file mode 100644
index 0159779..0000000
--- a/talermerchantdemos/blog/templates/index.html
+++ /dev/null
@@ -1,40 +0,0 @@
-{% extends "templates/base.html" %}
-{% block main %}
- <h1>Essay Shop: Free Software, Free Society</h1>
- <div style="font-size: smaller;">
- <p>This is the second edition of <cite>Free Software, Free Society: Selected Essays of Richard M. Stallman.</cite><br>
- Free Software Foundation<br>
- 51 Franklin Street, Fifth Floor<br>
- Boston, MA 02110-1335
- <br>
- Copyright &copy; 2002, 2010 Free Software Foundation, Inc.
- </p>
-
- <p>Verbatim copying and distribution of this entire book are permitted
- worldwide, without royalty, in any medium, provided this notice is
- preserved. Permission is granted to copy and distribute translations
- of this book from the original English into another language provided
- the translation has been approved by the Free Software Foundation and
- the copyright notice and this permission notice are preserved on all
- copies.
- </p>
- <p>ISBN 978-0-9831592-0-9</p>
- </div>
-
- <h2>Chapters</h2>
- <div>
- Click on an individual chapter to to purchase it. You can
- get free, virtual money to buy articles on this page at the <a href="{{ env('TALER_ENV_URL_BANK', '#') }}">bank</a>.
- </div>
-
- <div>
- {% for article in articles %}
- <div class="notice">
- <h3><a href="{{ url_for('article', article_name=article.slug) }}">{{article.title}}</a></h3>
- <p>{{ article.teaser|safe }} <a href="{{ url_for('article', article_name=article.slug) }}">(Pay to read more...)</a></p>
- </div>
- {% else %}
- <em>(No articles available)</em>
- {% endfor %}
- </div>
-{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/show_refund.html b/talermerchantdemos/blog/templates/show_refund.html
deleted file mode 100644
index 913b6a5..0000000
--- a/talermerchantdemos/blog/templates/show_refund.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% extends "templates/base.html" %}
-
-{% block main %}
-
-<h1>Refund</h1>
-
-<div class="taler-installed-hide">
- <p>
- Looks like your browser doesn't support GNU Taler payments. You can try
- installing a <a href="https://taler.net/en/wallet.html">wallet browser extension</a>.
- </p>
-</div>
-
-<div>
-
- <p>
- You can use this QR code to get a refund with your mobile wallet:
- </p>
-
- {{ qrcode_svg | safe }}
-
- <p>
- Click <a href="{{ taler_refund_uri }}">this link</a> to open your system's Taler wallet if it exists.
- </p>
-
-</div>
-
-{% endblock main %}
diff --git a/talermerchantdemos/static/__init__.py b/talermerchantdemos/static/__init__.py
new file mode 100644
index 0000000..6b61bdc
--- /dev/null
+++ b/talermerchantdemos/static/__init__.py
@@ -0,0 +1 @@
+app.static_folder = 'static'
diff --git a/talermerchantdemos/static/demo.css b/talermerchantdemos/static/demo.scss
index b2688ad..7733515 100644
--- a/talermerchantdemos/static/demo.css
+++ b/talermerchantdemos/static/demo.scss
@@ -44,26 +44,39 @@
}
body {
- overflow-y: scroll;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+body * {
+ /* margin-left: 0.5vw; */
+ left: 0.1vw;
}
@media (min-width: 500px) {
.content {
- margin-left: 25%;
+ /* margin-left: 0vw; */
padding-left: 2em;
margin-right: 1em;
- overflow-x: auto;
+ overflow-x: hidden;
+ overflow-y: auto;
}
.demobar {
- height: 100%;
+ /* height: 100%; */
+ /* width: 25%; */
+ /* NOTE: Please use "vh"/"vw" instead of "%" when possible, in the future. */
+ height: min-content;
+ width: 100vw;
margin: 0;
top: 0;
left: 0;
background-color: #033;
color: white;
- position: fixed;
- width: 25%;
+ position: relative;
padding-right: 1em;
- overflow: auto;
+ overflow-x: hidden;
+ overflow-y: auto;
+ text-align: center;
}
}
+
diff --git a/talermerchantdemos/static/navbar.css b/talermerchantdemos/static/navbar.css
new file mode 100644
index 0000000..aabacc3
--- /dev/null
+++ b/talermerchantdemos/static/navbar.css
@@ -0,0 +1,75 @@
+/**
+ * @author Torsten Grothoff
+ * @name navbar.css
+ * @description Makes the navigation bar have styles
+ * @license LGPL-3.0-or-later
+ */
+.navcontainer {
+ overflow: hidden;
+ background: #144;
+ margin-bottom: 50px;
+ width: 100%;
+ color: white;
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0px;
+ width: 100vw;
+ backdrop-filter: blur(10px);
+ opacity: 1;
+ z-index: 10000;
+}
+
+nav {
+ left: 1vw;
+ position: relative;
+ background: #144;
+ z-index: 10000;
+}
+
+nav a, nav span, .navbtn {
+ border: none;
+ color: white;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ background: #00000000;
+ height: inherit;
+}
+
+nav a, nav span, .navbtn {
+ padding: 15px 32px;
+}
+
+nav a:hover, nav span:hover, .navbtn:hover {
+ background: #00000022;
+}
+
+nav a.active, nav span.active, .navbtn.active {
+ background-color: #4CAF50;
+}
+
+nav a.active:hover, nav span.active:hover, .navbtn.active:hover {
+ background: #377c39;
+}
+
+nav a, nav span, .navbtn {
+ cursor: pointer;
+}
+
+nav .right {
+ float: right;
+ margin-right: 5vw;
+}
+nav .right div.nav {
+ display: none;
+}
+nav .right div.nav:hover {
+ display: block;
+}
+
+nav .right:hover div.nav {
+ display: block;
+}
+
+/*# sourceMappingURL=navbar.css.map */
diff --git a/talermerchantdemos/static/navbar.css.map b/talermerchantdemos/static/navbar.css.map
new file mode 100644
index 0000000..1725bd6
--- /dev/null
+++ b/talermerchantdemos/static/navbar.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["navbar.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;;;AAEJ;EAiCI;EACA;;AAPA;EACI;;AAEJ;EACI;;;AAMJ;EACI","file":"navbar.css"} \ No newline at end of file
diff --git a/talermerchantdemos/static/navbar.scss b/talermerchantdemos/static/navbar.scss
new file mode 100644
index 0000000..312bf0c
--- /dev/null
+++ b/talermerchantdemos/static/navbar.scss
@@ -0,0 +1,103 @@
+/**
+ * @author Torsten Grothoff
+ * @name navbar.css
+ * @description Makes the navigation bar have styles
+ * @license LGPL-3.0-or-later
+ */
+
+
+.navcontainer{
+ overflow:hidden;
+ background:#144;
+ margin-bottom:50px;
+ width:100%;
+ color:white;
+ position:-webkit-sticky;
+ position:sticky;
+ top:0px;
+ width: 100vw;
+ backdrop-filter: blur(10px);
+ opacity: 1;
+ z-index: 10000;
+}
+nav {
+ left: 1vw;
+ position: relative;
+ background:#144;
+ z-index: 10000;
+}
+
+nav a, nav span,.navbtn {
+ border: none;
+ color: white;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ background: #00000000;
+ height: inherit;
+}
+nav a, nav span,.navbtn{
+ padding: 15px 32px;
+}
+
+nav a:hover, nav span:hover,.navbtn:hover {
+ background: #00000022;
+}
+
+nav a.active, nav span.active,.navbtn.active {
+ background-color: #4CAF50;
+}
+
+nav a.active:hover, nav span.active:hover,.navbtn.active:hover {
+ background: #377c39;
+}
+
+nav a, nav span,.navbtn {
+ cursor: pointer;
+}
+nav .right {
+ // input[type=checkbox] {
+ // // // opacity: 0;
+ // // // position: absolute;
+ // // position: relative;
+ // // left:0.5vw;
+ // // // top:0;
+ // // width: inherit;
+ // // height: inherit;
+
+ // // $sx: 1.5;
+ // // $sy: 1.5;
+
+ // // -ms-transform: scale($sx,$sy); /* IE */
+ // // -moz-transform: scale($sx,$sy); /* FF */
+ // // -webkit-transform: scale($sx,$sy); /* Safari and Chrome */
+ // // -o-transform: scale($sx,$sy); /* Opera */
+ // // transform: scale($sx,$sy);
+ // // float:right;
+ // opacity: 0;
+ // }
+ // input[type=checkbox]:checked + div.nav {
+ // display: block;
+ // }
+ // input[type=checkbox] + div.nav {
+ // display: none;
+ // }
+ div.nav {
+ display: none;
+ }
+ div.nav:hover {
+ display: block;
+ }
+ float:right;
+ margin-right: 5vw;
+}
+nav .right:hover {
+ div.nav {
+ display:block;
+ }
+}
+
+// input[type=checkbox]:checked + label {
+// color: red;
+// } \ No newline at end of file
diff --git a/talermerchantdemos/static/pure.css b/talermerchantdemos/static/pure.scss
index 7391139..9b555ed 100644
--- a/talermerchantdemos/static/pure.css
+++ b/talermerchantdemos/static/pure.scss
@@ -821,7 +821,7 @@ this the same font stack that Normalize.css sets for the `body`.
.pure-button-active,
.pure-button:active {
box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset;
- border-color: #000\9;
+ border-color: #000;
}
.pure-button[disabled],
diff --git a/talermerchantdemos/static/taler-fallback.css b/talermerchantdemos/static/taler-fallback.scss
index e403d71..e403d71 100644
--- a/talermerchantdemos/static/taler-fallback.css
+++ b/talermerchantdemos/static/taler-fallback.scss