changeset 230:0e69b5843c2f

theme: bulma theme first draft: This theme uses the Bulma CSS framework, Brython to handle the menu on touch devices, and Sass to customize Bulma. CSS default fallbacks are disabled as Bulma uses its own naming conventions, and default fallbacks would lead to hard to debug conflicts. `common.js` has been slightly improved to handle custom classed in `tab_select` The theme is not complete yet, but it is functional.
author Goffi <goffi@goffi.org>
date Tue, 19 May 2020 00:02:34 +0200
parents 739c3e6999fa
children aa37750c2617
files sat_templates/templates/bulma/_browser/__init__.py sat_templates/templates/bulma/_browser/browser_meta.json sat_templates/templates/bulma/_browser/bulma_sat.scss sat_templates/templates/bulma/base/base.html sat_templates/templates/bulma/blog/articles.html sat_templates/templates/bulma/blog/atom.xml sat_templates/templates/bulma/blog/discover.html sat_templates/templates/bulma/blog/item.html sat_templates/templates/bulma/blog/macros.html sat_templates/templates/bulma/chat/chat.html sat_templates/templates/bulma/chat/message.html sat_templates/templates/bulma/chat/select.html sat_templates/templates/bulma/components/avatar.html sat_templates/templates/bulma/components/block.html sat_templates/templates/bulma/components/common.html sat_templates/templates/bulma/components/images.html sat_templates/templates/bulma/components/menu_labels.html sat_templates/templates/bulma/event/admin.html sat_templates/templates/bulma/event/attendance.html sat_templates/templates/bulma/event/counter.html sat_templates/templates/bulma/event/create.html sat_templates/templates/bulma/event/invitation.html sat_templates/templates/bulma/event/overview.html sat_templates/templates/bulma/file/discover.html sat_templates/templates/bulma/file/overview.html sat_templates/templates/bulma/forum/overview.html sat_templates/templates/bulma/forum/view.html sat_templates/templates/bulma/forum/view_topics.html sat_templates/templates/bulma/input/field.html sat_templates/templates/bulma/input/form.html sat_templates/templates/bulma/input/navigation.html sat_templates/templates/bulma/input/textbox.html sat_templates/templates/bulma/input/xmlui.html sat_templates/templates/bulma/login/logged.html sat_templates/templates/bulma/login/login.html sat_templates/templates/bulma/login/register.html sat_templates/templates/bulma/merge-request/create.html sat_templates/templates/bulma/merge-request/discover.html sat_templates/templates/bulma/merge-request/edit.html sat_templates/templates/bulma/merge-request/item.html sat_templates/templates/bulma/photo/album.html sat_templates/templates/bulma/photo/discover.html sat_templates/templates/bulma/settings.json sat_templates/templates/bulma/static/highlight.css sat_templates/templates/bulma/static/styles.css sat_templates/templates/bulma/static/styles_noscript.css sat_templates/templates/bulma/ticket/create.html sat_templates/templates/bulma/ticket/discover.html sat_templates/templates/bulma/ticket/edit.html sat_templates/templates/bulma/ticket/item.html sat_templates/templates/bulma/ticket/overview.html sat_templates/templates/bulma/ticket/tickets.html sat_templates/templates/default/static/common.js
diffstat 53 files changed, 2415 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/_browser/__init__.py	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,7 @@
+from browser import bind, document
+
+
+@bind("#main_menu_burger", "click")
+def burger_click(ev):
+    document["main_menu"].classList.toggle('is-active')
+    document["main_menu_burger"].classList.toggle('is-active')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/_browser/browser_meta.json	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,9 @@
+{
+    "js": {
+        "package": {
+            "dependencies": {
+                "bulma": "latest"
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/_browser/bulma_sat.scss	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,6 @@
+@charset "utf-8";
+$primary: #82baff;
+$body-background-color: #eaeaea;
+
+@import "node_modules/bulma/bulma.sass";
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/base/base.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,85 @@
+ {# embedded is set to avoid including base.html several times if a generic page is included (e.g. blog/articles.html) #}
+{% set embedded = True %}
+{% import 'components/common.html' as component with context %}
+{{ script.include('common', '') }} {# common.js is, as its name states, a common script, so it's useful to import it here #}
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    {# using SVG directly doesn't always play well with Bulma, so we also use the icon font #}
+    <link rel='stylesheet' type="text/css" href='{{media_path}}fonts/fontello/css/fontello.css'>
+    <link rel='stylesheet' type="text/css" href='{{build_path}}bulma_sat.css'>
+
+    {% if norobots %}
+        <meta name="robots" content="noindex, nofollow">
+    {% endif %}
+
+    <title>{% block title %}{{C.APP_NAME}}{% endblock %}</title>
+
+    {% if css_content is defined %}
+        <style type="text/css">
+            {{css_content}}
+        </style>
+        {% if css_content_noscript is defined %}
+        <noscript>
+            <style type="text/css">
+                {{css_content_noscript}}
+            </style>
+        </noscript>
+        {% endif %}
+    {% else %}
+        {% for css_file in css_files %}
+            <link rel='stylesheet' type="text/css" href='{{css_file}}'>
+        {% endfor %}
+        {% if css_files_noscript %}
+            <noscript>
+                {% for css_file in css_files_noscript %}
+                    <link rel='stylesheet' type="text/css" href='{{css_file}}'>
+                {% endfor %}
+            </noscript>
+        {% endif %}
+    {% endif %}
+
+    {% if links is defined %}
+        {% for link_data in links %}
+            <link{{link_data|xmlattr}}>
+        {% endfor %}
+    {% endif %}
+
+    {% if xmpp_uri is defined %}
+        <link rel="alternate" type="application/atom+xml" href="{{xmpp_uri}}" >
+    {% endif %}
+
+    {% if dynamic_style is defined %}
+    {# be extra careful about dynamic style, insure escaping if you use untrusted values ! #}
+        <style type="text/css">
+        {{dynamic_style}}
+        </style>
+    {% endif %}
+
+    {{ script.generate_scripts() }}
+
+    {% for script in scripts %}
+        <script{{ {'src': script.src, 'type': script.type} | xmlattr }}>{{script.content}}</script>
+    {% endfor %}
+
+    {% block favicon %}
+        <link rel="icon" href="{{media_path}}icons/apps/64/sat.png">
+    {% endblock favicon %}
+</head>
+<body{{ {'onload': body_onload} | xmlattr }}>
+    {% if atom_url is defined %}
+        {{ icon_defs('feed') }}
+    {% endif %}
+    {% if main_menu %}
+        {% block main_menu %}
+            {{ component.menu(main_menu, class="main_menu") }}
+        {% endblock main_menu %}
+    {% endif %}
+    <div id="body" class="container">
+        {% block body %}
+        {% endblock body %}
+    </div>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/blog/articles.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,40 @@
+{# blog_page indicate if blog is included in an other page or if it is the main one #}
+{% if not embedded %}{% extends 'base/base.html' %}{% set blog_page = True %}{% endif %}
+{% set single = items|length == 1 %}
+{% set dates_format='relative' if single else 'short' %}
+{% import 'components/avatar.html' as avatar with context %}
+{% import 'blog/macros.html' as blog with context %}
+{% import 'input/navigation.html' as navigation with context %}
+
+{%- block title scoped -%}
+    {%- if blog_page -%}
+        {%- if single -%}
+            {{- items[0].title|default(items[0].content, true)|truncate(60, True, '…') + ' - ' + C.APP_NAME -}}
+        {%- else -%}
+            {{C.APP_NAME}}
+            {# {{- super() -}}
+               FIXME: super() is failing if blog is embedded (i.e. base/base.html is not its direct parent)
+                      not sure what's the best way to avoid that, so just using C.APP_NAME for now #}
+        {%- endif -%}
+    {%- endif -%}
+{%- endblock title -%}
+
+{% block body %}
+{% if items %}
+    <div class="container has-margin-top-1">
+        <div id="blog_items" class="columns">
+            <div class="column">
+                {{ blog.show_items(items, expanded=single) }}
+            </div>
+        </div>
+    </div>
+{% else %}
+    <div class="message has-margin-top-1">
+        <div class="message-body">
+            {% trans %}No articles found in this blog!{% endtrans %}
+        </div>
+    </div>
+{% endif %}
+
+{{ navigation.prev_next(_("newer articles"), _("older articles")) }}
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/blog/atom.xml	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns='http://www.w3.org/2005/Atom'>
+    {% if title is defined %}
+        <title>{{title}}</title>
+    {% elif target_profile is defined %}
+        <title>{% trans name=target_profile%}{{name}}'s blog{% endtrans %}</title>
+    {% else %}
+        <title>{% trans app_name=C.APP_NAME%}{{app_name}} blog{% endtrans %}</title>
+    {% endif %}
+    <link href='{{request_uri}}' type='application/atom+xml' rel='self'/>
+    <link href='{{http_uri}}' type='text/html' rel='alternate'/>
+    <link href='{{xmpp_uri}}' type='application/atom+xml' rel='alternate'/>
+    <id>{{xmpp_uri}}</id>
+    <updated>{{updated|date_fmt('iso')}}</updated>
+    {% for item in items %}
+        <entry>
+            {% if item.title_xhtml %}
+                <title type='xhtml'>{{item.title_xhtml}}</title>
+            {% else %}
+                <title>{{item.title|default(item.content|truncate(80, True, '…'), True)}}</title>
+            {% endif %}
+            <link href='{{items_http_uri[item.id]}}' type='text/html' rel='alternate'/>
+            <link href='{{item.uri}}' type='application/atom+xml' rel='alternate'/>
+            <id>{{item.uri}}</id>
+            <updated>{{item.updated|date_fmt('iso')}}</updated>
+            <published>{{item.published|date_fmt('iso')}}</published>
+            <author>
+                <name>{{item.author}}</name>
+                <uri>xmpp:{{item.author_jid}}</uri>
+            </author>
+            {% for tag in item.tags %}
+                <category term="{{tag}}"/>
+            {% endfor %}
+            {% if item.content_xhtml %}
+                <content type='xhtml'>
+                    <div xmlns='http://www.w3.org/1999/xhtml'>
+                    {{item.content_xhtml}}
+                    </div>
+                </content>
+            {% else %}
+                <content type='text'>
+                    {{item.content_txt}}
+                </content>
+            {% endif %}
+        </entry>
+    {% endfor %}
+</feed>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/blog/discover.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,47 @@
+{% extends 'base/base.html' %}
+{% import 'components/block.html' as block %}
+{% import 'components/images.html' as images with context %}
+{% import 'components/avatar.html' as avatar with context %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+{% block body %}
+{{ icon_defs('blog') }}
+
+<section class="section">
+    <article class="message has-text-centered">
+        <div class="message-body">
+            {% trans %}
+            Please select the blog you want to consult
+            {% endtrans %}
+        </div>
+    </article>
+</section>
+
+<section class="section disco_blogs">
+    <div class="columns is-mobile is-multiline">
+        {% for entity in disco_entities %}
+            <div class="column is-2-desktop is-4-touch">
+                <div class="card x-is-hoverable">
+                    <a href="{{entities_url[entity]}}" class="items_vert--centered">
+                        <div class="card-image is-flex has-items-centered has-padding-1">
+                            {{ avatar.avatar(entity, "is-64x64") }}
+                        </div>
+                        <div class="card-content has-text-centered has-text-shortenable has-padding-1">
+                            <span>{{ identities[entity].nicknames[0] if identities[entity].nicknames else entity }}</span>
+                        </div>
+                    </a>
+                </div>
+            </div>
+        {% endfor %}
+    </div>
+</section>
+
+<section class="section">
+    <p class="content">{% trans %}Or enter the jid of a blog writer{% endtrans %}</p>
+    {% call form.form(class="form--single") %}
+        {{ field.text("jid", _("blog writer jid"), required=true)}}
+        {{ field.submit(_("Consult")) }}
+    {% endcall %}
+</section>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/blog/item.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,67 @@
+{# display a blog item which can be expanded/retracted by clicking on it
+
+    if the locale differs from item language, it will be totally reduced, and will need a click to be displayed
+
+    @variable item(data_object.BlogItem): item to display
+    @variable identities(data_object.Identities): identities which can be used to display info on item author
+    @variable dates_format(unicode): format of the date to use (see date_fmt filter)
+#}
+
+{% block item %}
+
+{% set item_level = (item_level or 0) + 1 %}
+
+{% if item.language and locale and locale.language != item.language %}
+    {# we may display items in different language in a specific way #}
+    {% set other_lang = " other_lang" if expanded else " other_lang state_init" %}
+{% endif %}
+    <article id="{{item.id}}" class="media has-background-white has-padding-1" >
+        {% if identities is defined %}
+            {% if avatar is defined %}
+                <div class="media-left">
+                    {{ avatar.avatar(item.author_jid) }}
+                </div>
+            {% endif %}
+        {% endif %}
+        <div class="media-content">
+            {% set title = item.title_xhtml or item.title%}
+            {% if title %}
+                <h4 class="title is-4">{{title}}</h1>
+            {% endif %}
+            <div class="content">
+                <p class="subtitle is-6 has-text-grey">
+                    {% set published = item.published|date_fmt(fmt=dates_format) %}
+                    <strong>{{item.author}}</strong> <small>{{published}}</small>
+                    {% if item.tags %}
+                        <small class="labels">
+                            {% if tags_http_uri is defined %}
+                                {% for tag in item.tags %}
+                                    <a href="{{tags_http_uri[tag]}}"><span class="tag is-rounded">{{tag}}</span></a>
+                                {% endfor %}
+                            {% else %}
+                                {% for tag in item.tags %}
+                                    <span class="tag">{{tag}}</span>
+                                {% endfor %}
+                            {% endif %}
+                        </small>
+                    {% endif %}
+                </p>
+                <p>
+                    {{- item.content_xhtml or item.content|urlize or '' -}}
+                </p>
+                {% for comments_items in item.comments_items_list %}
+                    {% for item in comments_items %}
+                        {% include 'blog/item.html' %}
+                    {% endfor %}
+                    {% if allow_commenting and item_level == 1 %}
+                        <div class="comment_post">
+                            {{- textbox.comment(service=comments_items.service, node=comments_items.node) -}}
+                        </div>
+                    {% endif %}
+                {% endfor %}
+
+            </div>
+        </div>
+    </article>
+
+{% endblock item %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/blog/macros.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,17 @@
+{% import 'input/textbox.html' as textbox with context %}
+
+{% macro show_items(items, comment=False, expanded=false, dates_fmt=none) %}
+    {# show items and comments items if present after each item,
+        then post form if allow_commenting is set
+        @param items(BlogItems): items to show
+        @param comment(bool): True items are comments
+            if False, a div with "main_article" class will be added
+        @param expanded(bool): initial state of items
+    #}
+    {% if dates_format is undefined %}
+        {% set dates_format = dates_fmt or 'short' %}
+    {% endif %}
+    {% for item in items %}
+        {% include 'blog/item.html' %}
+    {% endfor %}
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/chat/chat.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,20 @@
+{{ script.include('chat') }}
+{% if not embedded %}{% extends 'base/base.html' %}{% endif %}
+{% block title %}{{ target_jid }} - {{ super() }}{% endblock %}
+{% block body %}
+<div class="container has-background-white has-padding-1">
+    <div id="messages">
+    {% if subject is defined %}
+        <div class="notification is-primary">
+            {{- subject|urlize(nofollow=true,target='_blank') -}}
+        </div>
+    {% endif %}
+    {% for msg in messages %}
+        {% include 'chat/message.html' %}
+    {% endfor %}
+    </div>
+    <textarea id="message_input" class="textarea has-margin-top-1 chat_input" name="message" type="text" rows="1" placeholder="{{_("enter your message")}}"></textarea>
+</div>
+{% endblock body %}
+
+{% block footer %}{% endblock footer %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/chat/message.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,31 @@
+{% import 'components/avatar.html' as avatar with context %}
+
+<div id="{{msg.id}}" class="media is-chat-message msg_{{msg.type}} {{'own_msg' if msg.from_ == own_jid.full()}}">
+    {%- if msg.type != C.MESS_TYPE_INFO %}
+        {%- set author = identities[msg.from_].nicknames[0] | default(msg.from_) -%}
+        <figure class="media-left">
+            {{ avatar.avatar(msg.from_, "is-32x32") }}
+        </figure>
+    {% endif -%}
+    <div class="media-content">
+        <div class="content">
+            {%- if msg.type != C.MESS_TYPE_INFO %}
+                <nav class="level is-mobile is-marginless is-size-7">
+                    <div class="level-left has-text-weight-bold">
+                        <div class="level-item">
+                            <span class="author">{{author}}</span>
+                        </div>
+                    </div>
+                    <div class="level-right is-italic">
+                        <div class="level-item">
+                            <span class="date">{{msg.timestamp|date_fmt('auto_day')}}</span>
+                        </div>
+                    </div>
+                </nav>
+            {% endif -%}
+            <p class="msg_body">
+            {{- msg.html or (msg.text|urlize(nofollow=true, target="_blank")) -}}
+            </p>
+        </div>
+    </div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/chat/select.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,43 @@
+{% if not embedded %}{% extends 'base/base.html' %}{% endif %}
+{% import 'components/avatar.html' as avatar with context %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+{% block body %}
+
+<section class="section">
+    <div class="message">
+        <div class="message-body">
+            {% trans %}Please select the chat room you want to enter{% endtrans %}
+        </div>
+    </div>
+</section>
+
+<section class="section">
+    <div class="columns is-mobile is-muliline">
+        {% for room in rooms %}
+            <div class="column is-2-desktop is-4-touch">
+                <div class="card x-is-hoverable">
+                    <a href="{{room.url}}">
+                        <div class="card-image is-flex has-items-centered has-padding-1">
+                            {{ avatar.avatar(room.jid, "is-64x64") }}
+                        </div>
+                        <div class="card-content has-text-centered has-text-shortenable has-padding-1">
+                            <span>{{room.name}}</span>
+                        </div>
+                    </a>
+                </div>
+            </div>
+        {% endfor %}
+    </div>
+</section>
+
+<section class="section">
+    <p class="content">{% trans %}Or enter a room address{% endtrans %}</p>
+    {% call form.form(class="form--single") %}
+        {{ field.text("jid", _("Room address (JID)"), required=true)}}
+        {{ field.submit(_("Join")) }}
+    {% endcall %}
+</section>
+
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/components/avatar.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,13 @@
+{% macro avatar(jid, class="is-48x48") %}
+    {%- if identities is defined -%}
+        {% set identity = identities[jid] %}
+        {%- if identity.avatar_basename is defined %}
+        <div class="is-avatar image {{class}}">
+            <img src="/cache/common/{{identities[jid].avatar_basename}}">
+        </div>
+        {% else %}
+            {% set nick = identity.nicknames[0] if identity.nicknames else jid %}
+            <span class="is-avatar image {{class}} is-flex has-items-centered has-text-weight-bold is-size-3 {{class}}">{{nick|first|upper}}</span>
+        {%- endif -%}
+    {%- endif -%}
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/components/block.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,73 @@
+{% macro separator(label, align='center') %}
+{# display a bloc separator
+    @param label(unicode): label to show
+    @param align(unicode): one of "left", "center", "right"
+#}
+    <div class="block_separator">
+        {% if align in ('center', 'right') %}
+            <div class="block_separator__line"></div>
+        {% endif %}
+        <div class="block_separator__label">
+            {{label}}
+        </div>
+        {% if align in ('center', 'left') %}
+            <div class="block_separator__line"></div>
+        {% endif %}
+    </div>
+{% endmacro %}
+
+{% macro icon_item(icon_name, label, url, class="") %}
+    <div class="column is-2-desktop is-4-touch">
+        <div class="card {{class}}">
+            <a href="{{url}}">
+                <div class="card-image has-text-centered">
+                    {{ icon(icon_name, cls='image is-64x64 is-inline-block') }}
+                </div>
+                <div class='card-content has-text-centered has-text-shortenable is-paddingless'>
+                    <span>{{label}}</span>
+                </div>
+            </a>
+        </div>
+    </div>
+{% endmacro %}
+
+{% macro disco_icon_grid(disco_entities, icon_name) %}
+{# display discovered entities in a grid
+    @param disco_entities: entities which mush have a name and url key or attribute
+    @param icon_name: name of a defined icon
+#}
+    <div class="columns">
+        {% for disco_entity in disco_entities %}
+            {{ icon_item(icon_name, disco_entity.name, disco_entity.url) }}
+        {% endfor %}
+    </div>
+{% endmacro %}
+
+{% macro interests_grid(interests, default_icon_name) %}
+{# display list of interests
+    @param interests: list of interests
+    @param default_icon_name: name of a defined icon to use when no thumb_url is available
+#}
+<div class="columns is-multiline">
+    {% for interest in interests %}
+        <div class="column is-4">
+            <div class="card x-is-hoverable">
+                <a href="{{interest.url}}">
+                    <div class="card-image is-photo-thumbnail-container is-flex has-items-centered has-background-light">
+                        {% if interest.thumb_url %}
+                            <img class="is-photo-thumbnail" src="{{interest.thumb_url}}">
+                        {% else %}
+                            <div class="is-photo-thumbnail">
+                                {{ icon(default_icon_name, "image is-128x128") }}
+                            </div>
+                        {% endif %}
+                    </div>
+                    <div class="card-content has-text-centered">
+                        <span><em>{{ interest.name|default(_("unnamed")) }}</em></span>
+                    </div>
+                </a>
+            </div>
+        </div>
+    {% endfor %}
+</div>
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/components/common.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,40 @@
+{% import 'components/menu_labels.html' as ml with context %}
+{# we need to use "with context" to disable cache, needed for i18n #}
+
+{% macro menu(menus, class='') %}
+    <nav class="navbar has-background-primary">
+        <div class="navbar-brand">
+            <a class="navbar-item" href="/">
+              <img src="{{media_path}}icons/apps/64/sat.png">
+            </a>
+            <a role="button" id="main_menu_burger" class="navbar-burger burger" data-target="main_menu">
+                <span aria-hidden="true"></span>
+                <span aria-hidden="true"></span>
+                <span aria-hidden="true"></span>
+            </a>
+        </div>
+        <div id="main_menu" class="navbar-menu">
+            <div class="navbar-start">
+            {% for name, url in menus %}
+                  <a class="navbar-item" {{ {'href': url}|xmlattr }}>
+                    {{name}}
+                  </a>
+            {% endfor %}
+            </div>
+            <div class="navbar-end">
+                <div class="navbar-item has-dropdown x-is-hoverable">
+                    <span class="navbar-link">{{locale.language_name}}</span>
+                    <div class="navbar-dropdown">
+                        {% for l in locales|reject("eq", locale) %}
+                            <a class="navbar-item" href="?{{C.KEY_LANG}}={{l}}">{{l.language_name}}</a>
+                        {% endfor %}
+
+                </div>
+            </div>
+        </div>
+    </nav>
+{% endmacro %}
+
+{% macro action_button(url, label=_("create"), icon="plus-circled", class="is-primary is-rounded") %}
+    <a class="button {{class}}" {{ {"href":url} | xmlattr }}><i class="icon-{{icon}}"></i> {{label}}</a>
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/components/images.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,15 @@
+{% macro get_icon_client(ident, class='is-64x64') %}
+    {% if ident.client %}
+        {% if ident.client.pc %}
+            {{ icon('desktop', cls='image is-inline-block {}'.format(class)) }}
+        {% elif ident.client.phone %}
+            {{ icon('mobile', cls='image is-inline-block {}'.format(class)) }}
+        {% elif ident.client.web %}
+            {{ icon('globe', cls='image is-inline-block {}'.format(class)) }}
+        {% elif ident.client.console %}
+            {{ icon('terminal', cls='image is-inline-block {}'.format(class)) }}
+        {% else %}
+            {{ icon('desktop', cls='image is-inline-block {}'.format(class)) }}
+        {% endif %}
+    {% endif %}
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/components/menu_labels.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,17 @@
+{# menu labels, map from menu names to labels #}
+{% set label = {
+    'login': _('Session') if profile else _('Log in'),
+    'blog': _('Blog'),
+    'forums': _('Forums'),
+    'merge-requests': _('Merge requests'),
+    'merge-request_new': _('Create new merge request'),
+    'tickets': _('Tickets'),
+    'tickets_list': _('List tickets'),
+    'ticket_new': _('Create new ticket'),
+    'chat': _('Chat'),
+    'files': _('Files sharing'),
+    'events': _('Events'),
+    'event_new': _('Create an event'),
+    'photos': _('Photos albums'),
+    'app': _('Application'),
+} %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/event/admin.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,117 @@
+{% extends 'base/base.html' %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+{% import 'input/textbox.html' as textbox with context %}
+
+{% block body %}
+<section class="section">
+
+    <h1 class="title is-4">{% trans name=event.name %}{{name}} administration{% endtrans %}</h1>
+    <div class="card">
+        {% if event.image is defined %}
+            <div class="card-image is-photo-thumbnail-container is-poster is-flex has-items-centered has-background-light">
+                <img class='is-photo-thumbnail' src="{{event.image}}">
+            </div>
+        {% endif %}
+        {% if event.description is defined %}
+            <div class="card-content has-text-centered">
+                <p>{{event.description}}</p>
+            </div>
+        {% endif %}
+    </div>
+
+
+    {% include 'event/counter.html' %}
+
+    <div class="tab__container">
+        <div class="tabs">
+            <ul>
+                <li class="tab__btn is-active" onclick='tab_select(this, "tab_guests", btn_clicked_cls="is-active")'><a>{% trans %}Invitees{% endtrans %}</a></li>
+                <li class="tab__btn" onclick='tab_select(this,"tab_invitations", btn_clicked_cls="is-active")'><a>{% trans %}Invite people{% endtrans %}</a></li>
+                <li class="tab__btn" onclick='tab_select(this,"tab_new_post", btn_clicked_cls="is-active")'><a>{% trans %}Write a blog post{% endtrans %}</a></li>
+                <li class="tab__btn" onclick='tab_select(this,"tab_blog", btn_clicked_cls="is-active")'><a>{% trans %}Read event blog{% endtrans %}</a></li>
+            </ul>
+        </div>
+
+        <div class="tab__page state_clicked" id="tab_guests">
+            {% if invitees %}
+                <table class="table is-fullwidth is-hoverable">
+                    <thead>
+                        <tr>
+                            <th>{% trans %}name{% endtrans %}</th>
+                            <th>{% trans %}coming?{% endtrans %}</th>
+                            <th>{% trans %}guests{% endtrans %}</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        {% for name, data in invitees.items() %}
+                            <tr>
+                                <td>{{name}}</td>
+                                <td>{{data.attend|default('')}}</td>
+                                {% if data.attend == 'no' %}
+                                    <td>&nbsp;</td>
+                                {% else %}
+                                    <td>{{data.guests|default(0)}}</td>
+                                {% endif %}
+                            </tr>
+                        {% endfor %}
+                    </tbody>
+                    <tfoot>
+                        <tr class="is-selected">
+                            <td colspan=2 class="table__total">{% trans %}total expected{% endtrans %}</td>
+                            <td class="table__total_value">{{invitees_guests|default('0')}}</td>
+                        </tr>
+                    </tfoot>
+                </table>
+            {% else %}
+                <p class="message--info">{% trans %}No invitee has answered yet{% endtrans %}</p>
+            {% endif %}
+        </div>
+
+
+        <div class="tab__page" id="tab_invitations">
+            {% call form.form(class="form--paper form__panel--vertical form__panel--center") %}
+                {{ textbox.head(event_service, event_node, 'event') }}
+                {{ field.meta('event_id', event_id) }}
+                {{ field.textarea("jids",
+                                  _("enter here a list of jid (one per line) to invite"),
+                                  )
+                                  }}
+                {{ field.textarea("emails",
+                                  _("enter here a list of emails addresses (one per line) to invite"),
+                                  )
+                                  }}
+                {{ field.submit(_("Invite people")) }}
+            {% endcall %}
+        </div>
+
+        <div class="tab__page" id="tab_new_post">
+            {% call form.form(class="form--paper form__panel--vertical form__panel--center") %}
+                {{ textbox.head(service, node, 'blog') }}
+                {{ field.text("title",
+                              _("title"),
+                              class="form__field--big") }}
+                {{ field.textarea("body",
+                                  _("body"),
+                                  )
+                                  }}
+                {{ field.text("language",
+                              _("language"),
+                              class="form__field--tiny") }}
+                {{ field.checkbox("comments",
+                                  _("allow comments"),
+                                  checked=true) }}
+                {{ field.submit(_("send"), class="has-margin-top-1") }}
+            {% endcall %}
+        </div>
+
+        <div class="tab__page" id="tab_blog">
+            {% if items is defined %}
+                {% include 'blog/articles.html' %}
+            {% endif %}
+        </div>
+
+    </div>
+</section>
+
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/event/attendance.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,24 @@
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+
+<div class="message">
+    <div class="message-body is-info">
+        {% trans %}Please indicate if you plan to attend the event:{% endtrans %}
+    </div>
+</div>
+{% call form.form() %}
+{{ field.meta("type", "attendance") }}
+{{ field.meta("service", event.invitees_service) }}
+{{ field.meta("node", event.invitees_node) }}
+<div class="box attending">
+    {{ field.choices("attend", (("yes", _("yes")), ("no", _("no")), ("maybe", _("maybe"))), checked=invitee.attend) }}
+</div>
+<div class="guests">
+    {{ field.int("guests", label=_("How many people will come (including you)?"), init=invitee.get("guests", 1)) }}
+</div>
+<div class="submit has-margin-top-1">
+    {{ field.submit() }}
+</div>
+{% endcall %}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/event/counter.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,14 @@
+{% if days_left is defined %}
+    {% if days_left > 0 %}
+        <div class="notification is-success has-text-centered has-text-weight-bold has-margin-top-1">
+            {% trans %}
+                {{days_left}} day left
+            {% pluralize %}
+                {{days_left}} days left
+            {% endtrans %}
+        </div>
+    {% else %}
+        <div class="notification is-danger has-text-centered has-text-weight-bold has-margin-top-1">{% trans %}the event is finished{% endtrans %}</div>
+    {% endif %}
+
+{% endif %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/event/create.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,31 @@
+{# create a new event #}
+
+{% extends 'base/base.html' %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+{% block body %}
+<section class="section">
+    <h3 class='title is-3'>
+        {% trans %}Create an event{% endtrans %}
+    </h3>
+    {% call form.form(class="form--paper form__panel--vertical") %}
+        {{ field.text("name", _("name"), required=true) }}
+        {{ field.text("location", _("location"), required=true) }}
+        {{ field.textarea("body",
+        _("description of the event"),
+        required=true,
+        ) }}
+        {{ field.date("date", _("date of the event"), required=true) }}
+        {{ field.url("main_image", _("event image URL (https)"),
+        title=_("you can enter here the URL to a JPEG or PNG image to use as representation of your event"),
+        placeholder=_("JPEG or PNG image URL"),
+        pattern='http.*(jpg|jpeg|png)') }}
+        {{ field.url("bg_image", _("background image URL (https)"),
+        title=_("you can enter here the URL to a JPEG or PNG image to use repeating background"),
+        placeholder=_("JPEG or PNG image URL"),
+        pattern='http.*(jpg|jpeg|png)') }}
+        {{ field.submit(_("Create event")) }}
+    {% endcall %}
+</section>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/event/invitation.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,37 @@
+{% extends 'base/base.html' %}
+
+{% block body %}
+
+<section class="section">
+    <h1 class="title is-4">
+        {% trans %}Welcome {{name}}{% endtrans %}
+    </h1>
+    <div class="notification is-info">
+        {% trans %}You have been invited to participate to an event{% endtrans %}
+    </div>
+    <div class="card">
+        {% if event.image is defined %}
+            <div class="card-image is-photo-thumbnail-container is-poster is-flex has-items-centered has-background-light">
+                <img class='is-photo-thumbnail' src="{{event.image}}">
+            </div>
+        {% endif %}
+        {% if event.description is defined %}
+            <div class="card-content has-text-centered">
+                <p>{{event.description}}</p>
+            </div>
+        {% endif %}
+    </div>
+
+
+    {% include 'event/counter.html' %}
+
+    {% if days_left is defined and days_left > 0 %}
+        {% include 'event/attendance.html' %}
+    {% endif %}
+
+    {% if items %}
+        {% include 'blog/articles.html' %}
+    {% endif %}
+</section>
+
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/event/overview.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,41 @@
+{# overview of current events
+
+    @variable item(xmlui_item): ticket to display
+    @variable comments(data_object.BlogItems): comments of the ticket
+    @variable comments_service(unicode): service for adding comments
+    @variable comments_node(unicode): node for adding comments
+#}
+
+{% if not embedded %}{% extends 'base/base.html' %}{% endif %}
+{% import 'components/block.html' as block with context %}
+
+{% block body %}
+{{ icon_defs('calendar') }}
+
+<section class="section">
+    <div class="message is-info">
+        <div class="message-body">
+            {% trans %}There is not events discovery yet, this will come in the future.{% endtrans %}
+        </div>
+    </div>
+
+    <div class="content has-items-centered is-flex">
+        {{ component.action_button(url_event_new) }}
+    </div>
+
+    {% if events is defined %}
+        <div class="message">
+            <div class="message-body">
+                {% trans nb_events=events|length%}
+                You have currently {{nb_events}} event in your personal list
+                {% pluralize %}
+                You have currently {{nb_events}} events in your personal list
+                {% endtrans %}
+            </div>
+        </div>
+
+        {{block.interests_grid(events, 'calendar')}}
+
+    {% endif %}
+</section>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/file/discover.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,72 @@
+{% extends 'base/base.html' %}
+{% import 'components/block.html' as block with context %}
+{% import 'components/images.html' as images with context %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+{% block body %}
+{{ icon_defs('server', 'desktop', 'mobile', 'globe', 'terminal') }}
+<section class="section">
+    <article class="message has-text-centered">
+        <div class="message-body">
+            {% trans %}
+            Please select the device you want to access
+            {% endtrans %}
+        </div>
+    </article>
+</section>
+<section class="section disco_files">
+    {% if disco_service_entities %}
+        <div class="files__services">
+            {{ block.separator(_("services")) }}
+            <div class="columns is-centered">
+                {% for entity,ident in disco_service_entities.items() %}
+                    {% if disco_service_entities|count == 1 %}
+                        {% set icon_label=_("your server") %}
+                    {% else %}
+                        {% set icon_label=(ident.values()|first).values()|first|first %}
+                    {% endif %}
+                    {{ block.icon_item('server', icon_label, entities_url[entity]) }}
+                {% endfor %}
+            </div>
+        </div>
+    {% endif %}
+    {% if disco_own_entities %}
+        <div class="files__own">
+            {{ block.separator(_("your devices")) }}
+            <div class="columns is-centered">
+                {% for entity,ident in disco_own_entities.items() %}
+                    {{ block.icon_item(
+                           icon_from_client(ident.client),
+                           (ident.values()|first).values()|first|first,
+                           entities_url[entity],
+                       )
+                    }}
+                {% endfor %}
+            </div>
+        </div>
+    {% endif %}
+    {% if disco_roster_entities %}
+        <div class="files__roster">
+            {{ block.separator(_("your contacts devices")) }}
+            <div class="columns is-centered">
+                {% for entity,ident in disco_roster_entities.items() %}
+                    {{ block.icon_item(
+                        icon_from_client(ident.client),
+                        entity.userhost(),
+                        entities_url[entity])
+                    }}
+                {% endfor %}
+            </div>
+        </div>
+    {% endif %}
+</section>
+
+<section class="section">
+    <p class="content">{% trans %}Or enter a full jid of a device{% endtrans %}</p>
+    {% call form.form(class="form--single") %}
+        {{ field.text("jid", _("device full jid"), required=true)}}
+        {{ field.submit(_("Access")) }}
+    {% endcall %}
+</section>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/file/overview.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,64 @@
+{% extends 'base/base.html' %}
+
+{% block body %}
+{{ icon_defs('level-up', 'doc','folder-open-empty', 'comment-empty') }}
+<section class="section">
+    <div class="columns files is-multiline is-mobile">
+        {% if parent_url is defined  %}
+            <div class="column is-2-desktop is-4-touch file">
+                <div class="card">
+                    <a href="{{ parent_url }}">
+                        <div class="card-image has-text-centered">
+                            {{ icon('level-up', cls='image is-64x64 is-inline-block') }}
+                        </div>
+                        <div class='card-content has-text-centered is-paddingless'>
+                            <span>{% trans %}parent dir{% endtrans %}</span>
+                        </div>
+                    </a>
+                </div>
+            </div>
+        {% endif %}
+
+        {% for file in files_data %}
+            {% if file.type == C.FILE_TYPE_DIRECTORY %}
+                <div class="column is-2-desktop is-4-touch file file_{{file.type}}">
+                    <div class="card">
+                        <a href="{{file.url}}">
+                            <div class="card-image has-text-centered">
+                                {{ icon('folder-open-empty', cls='image is-64x64 is-inline-block') }}
+                            </div>
+                            <div class='card-content has-text-centered has-text-shortenable is-paddingless'>
+                                <span>{{ file.name }}</span>
+                            </div>
+                        </a>
+                    </div>
+                </div>
+            {% else %}
+                <div class="column is-2-desktop is-4-touch file file_{{file.type}}">
+                    <div class="card">
+                        <a href="{{file.url}}">
+                            <div class="card-image has-text-centered">
+                                {% if file.thumb_url is defined %}
+                                    <img src="{{file.thumb_url}}" class="image is-64x64 is-inline-block" alt="{{file.name}}">
+                                {% else %}
+                                    {{ icon('doc', cls='image is-64x64 is-inline-block') }}
+                                {% endif %}
+                            </div>
+                            <div class='card-content has-text-centered has-text-shortenable is-paddingless'>
+                                <span>{{ file.name }}</span>
+                            </div>
+                        </a>
+                    </div>
+                </div>
+            {% endif %}
+        {% endfor %}
+    </div>
+</section>
+{% if not files_data %}
+    <article class="message has-text-centered">
+        <div class="message-body">
+            {% trans %}No files are shared in this directory!{% endtrans %}
+        </div>
+    </article>
+{% endif%}
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/forum/overview.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,51 @@
+{% extends 'base/base.html' %}
+
+{% macro generate_forums(forums_data, level=0) %}
+    {% if level == 0 %}
+        <section class="section">
+            {% for forum in forums_data %}
+                <div class="box content has-text-centered has-background-info has-margin-top-1">
+                    <h4 class="title has-text-white">{{ forum.title }}</h4>
+                    {% if 'short-desc' in forum %}
+                        <p class="subtitle is-size-7 is-italic has-text-white">{{ forum['short-desc'] }}</h5>
+                    {% endif %}
+                </div>
+                {% if 'sub-forums' in forum %}
+                    {{ generate_forums(forum['sub-forums'], level=level+1) }}
+                {% endif %}
+            {% endfor %}
+        </section>
+    {% else %}
+        <div class="forum forum__panel_{{panel_type}}">
+            {% for forum in forums_data %}
+                <div class="has-vmargin-1 forum forum__cat_{{panel_type}} forum__level_{{level}}">
+                    {% if 'http_url' in forum %}
+                        <a href="{{forum['http_url']}}" class="box content x-is-hoverable">
+                    {% else %}
+                        <a class="box content">
+                    {% endif %}
+                        <h4 class="title">{{ forum.title }}</h4>
+                        {% if 'short-desc' in forum %}
+                            <p class="subtitle is-size-7 is-italic">{{ forum['short-desc'] }}</p>
+                        {% endif %}
+                    </a>
+                    {% if 'sub-forums' in forum %}
+                        {{ generate_forums(forum['sub-forums'], level=level+1) }}
+                    {% endif %}
+                </div>
+            {% endfor %}
+        </div>
+{% endif %}
+{% endmacro %}
+
+{% block body %}
+{% if not forums %}
+    <div class="message">
+        <div class="message-body">{% trans %}No forums found on this server!{% endtrans %}</div>
+    </div>
+{% else %}
+    <div class="container forums">
+        {{ generate_forums(forums) }}
+    </div>
+{% endif %}
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/forum/view.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,21 @@
+{% if not embedded %}{% extends 'base/base.html' %}{% endif %}
+{% set dates_format='relative' if single else 'short' %}
+{% import 'components/avatar.html' as avatar with context %}
+{% import 'blog/macros.html' as blog with context %}
+{% import 'input/textbox.html' as textbox with context %}
+{% import 'input/navigation.html' as navigation with context %}
+
+{% block body %}
+
+
+<section class="section">
+    {{ blog.show_items(items, expanded=true) }}
+</section>
+
+<section class="section">
+    {{- textbox.comment_or_login(service=service, node=node, placeholder=_("Enter your message here")) -}}
+</section>
+
+
+{{ navigation.prev_next(_("newer articles"), _("older articles")) }}
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/forum/view_topics.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,49 @@
+{% if not embedded %}{% extends 'base/base.html' %}{% endif %}
+{% import 'input/field.html' as field with context%}
+{% import 'input/textbox.html' as textbox with context %}
+{% import 'input/navigation.html' as navigation with context %}
+{% import 'components/avatar.html' as avatar with context %}
+
+{% block body %}
+
+    {% if not topics %}
+        <div class="message">
+            <div class="message-body">
+                {% trans %}There is not message yet in this forum.{% endtrans %}
+                {% if profile %}
+                    {% trans %}You can start a topic of interest by filling this form.{% endtrans %}
+                {% else %}
+                    {% trans %}You can login to create a new topic.{% endtrans %}
+                {% endif %}
+            </div>
+        </div>
+    {% endif %}
+
+    <section class="section">
+        <div class="has-background-white has-padding-1">
+            {% for topic in topics %}
+                <div class="media has-items-vcentered">
+                    <div class="media-left">
+                        {{ avatar.avatar(topic.author) }}
+                    </div>
+                    <div class="media-content">
+                        <p class="is-size-5-desktop is-size-6-touch x-is-hoverable">
+                        <a href="{{topic.http_uri}}">
+                            {{topic.title}}
+                        </a>
+                        </p>
+                    </div>
+                </div>
+            {% endfor %}
+        </div>
+    </section>
+    {% if profile %}
+        <section class="section">
+            {% call textbox.textbox(service, node, placeholder=_("Your message"), submit_label=_("Create topic"), type="new_topic") %}
+            {{ field.text("title", placeholder=_("Your topic"), required=True) }}
+            {% endcall %}
+        </section>
+    {% endif %}
+
+    {{ navigation.prev_next(_("older topics"), _("newer topics")) }}
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/input/field.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,119 @@
+{# macros to create form fields #}
+
+{% macro field(type, name, label="", help="", required=false, icon_left=none, icon_right=none) %}
+    {# generic field
+       "class" keyword can be used to add classes
+       additional kwargs will be passed as attributes #}
+
+    <div class="field form_input {{kwargs.pop('class', '')}}">
+        {% set cur_id = name|next_gidx %}
+        {% if label %}
+            <label for="{{cur_id}}" class="label">{{label}}</label>
+        {% endif %}
+        <div class="control{% if icon_left %} has-icons-left{% endif %}{% if icon_right %} has-icons-right{% endif %}">
+            <input id="{{cur_id}}" class="input" type="{{type}}" name="{{name}}" {{"required" if required}} {{kwargs|xmlattr}}>
+            {% if icon_left %}
+                <span class="icon is-left">
+                    {# we use <i> with font from CSS instead of SVG, because using directly SVG doesn't play way with Bulma's control #}
+                    <i class="icon-{{icon_left}}"></i>
+                </span>
+            {% endif %}
+            {% if icon_right %}
+                <span class="icon is-right">
+                    <i class="icon-{{icon_right}}"></i>
+                </span>
+            {% endif %}
+        </div>
+        {% if help %}
+            <p class="help">{{help}}</p>
+        {% endif %}
+    </div>
+{% endmacro %}
+
+{% macro select(name, options_list, selected=none, required=false, multiple=false) %}
+    {# selection of elements with <select>
+
+    @param name: name of the field
+    @param options_list(list[str, str]): list of name, label of options
+    @param selected(none, list): list of select elements
+    @param required(bool): true this field is required
+    @param multiple(bool): true is multiple selection is possible
+    #}
+    <select name="{{name}}" class="form_input {{kwargs.pop('class', '')}}" {{"required" if required}}>
+    {% for value, label in options_list %}
+         <option value="{{value}}" {{'selected' if selected and value in selected}}>{{label}}</option>
+    {% endfor %}
+    </select>
+{% endmacro %}
+
+{% macro choices(name, choices_list, checked=none) %}
+    <div class="control {{kwargs.pop('class', '')}}">
+        {% for choice, label in choices_list %}
+            <label class="radio" for="{{name|cur_gidx}}">
+                <input id="{{name|next_gidx}}" type="radio" name="{{name}}" value="{{choice}}"{{" checked" if checked==choice}}> {{label}}
+                </label>
+        {% endfor %}
+    </div>
+{% endmacro %}
+
+{% macro int(name, label="", init=0) %}
+    {{ field("number", name=name, label=label, value=init, step=1, min=0, **kwargs) }}
+{% endmacro %}
+
+{% macro checkbox(name, label="", checked=false) %}
+    {% set cur_id = name|next_gidx %}
+    <label for="{{cur_id}}" class="label checkbox">{{label}}</label>
+    <input id="{{cur_id}}" type="checkbox" {% if checked %}checked=checked{% endif %}>
+{% endmacro %}
+
+{% macro text(name, label="", placeholder="", required=false) %}
+    {{ field("text", name=name, label=label, required=required, placeholder=placeholder, **kwargs) }}
+{% endmacro %}
+
+{% macro password(name, label="", required=false) %}
+    {{ field("password", name=name, label=label, required=required, **kwargs) }}
+{% endmacro %}
+
+{% macro email(name, label="", required=false) %}
+    {{ field("email", name=name, label=label, required=required, **kwargs) }}
+{% endmacro %}
+
+{% macro date(name, label="", required=false) %}
+    {{ field("date", name=name, label=label, required=required, **kwargs) }}
+{% endmacro %}
+
+{% macro url(name, label="", required=false) %}
+    {{ field("url", name=name, label=label, required=required, **kwargs) }}
+{% endmacro %}
+
+{% macro file(name, label="", required=false) %}
+    {{ field("file", name=name, label=label, required=required, **kwargs) }}
+{% endmacro %}
+
+{% macro textarea(name, label="", rows=none, cols=50, placeholder='', help='', required=false) %}
+    <div class="field form_input">
+        {% set cur_id = name|next_gidx %}
+        {% if label %}
+            <label for="{{cur_id}}" class="label">{{label}}</label>
+        {% endif %}
+        <textarea id="{{cur_id}}" name="{{name}}" {{ {"rows": rows, "cols": cols}|xmlattr }} placeholder="{{placeholder}}" {{"required" if required}} class="textarea"></textarea>
+        {% if help %}
+            <p class="help">{{help}}</p>
+        {% endif %}
+    </div>
+{% endmacro %}
+
+{% macro meta(name, value) %}
+    <input type="hidden" name="{{name}}" value="{{value}}">
+{% endmacro %}
+
+{% macro submit(text=_("Send"), id=none, class='') %}
+    {# submit button
+
+    @param text(str): label of the button
+    @param id(none, str): id of the element
+    #}
+    <div class="control {{class}}">
+      <button {{ 'id="{id}"'.format(id=id)|safe if id }} class="button is-primary form_submit {{kwargs.pop('class', '')}}" type="submit">{{text}}</button>
+    </div>
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/input/form.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,8 @@
+{% macro form(action='', class='') %}
+<form method="post" action="{{action}}" {{ {'class': class}|xmlattr }} {{kwargs|xmlattr}} >
+    {% if csrf_token is defined %}
+        <input type="hidden" name="csrf_token" value="{{csrf_token}}">
+    {% endif %}
+    {{ caller() }}
+</form>
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/input/navigation.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,16 @@
+{% macro prev_next(prev_label=_("newer"), next_label=_("older")) %}
+<section class="section">
+    <nav class="pagination is-rounded" role="navigation" aria-label="pagination">
+        {% if previous_page_url is defined %}
+            <a href="{{previous_page_url}}" class="pagination-previous has-background-white">{{prev_label}}</a>
+        {% else %}
+            <a class="pagination-previous pagination-disabled has-background-white has-text-grey-light has-hover-white-light">{{prev_label}}</a>
+        {% endif %}
+        {% if next_page_url is defined %}
+            <a href="{{next_page_url}}" class="pagination-next has-background-white">{{next_label}}</a>
+        {% else %}
+            <a class="pagination-next pagination-disabled has-background-white has-text-grey-light has-hover-white-light">{{prev_label}}</a>
+        {% endif %}
+    </nav>
+</section>
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/input/textbox.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,79 @@
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+{% macro head(service, node, type="textbox") %}
+    {# include data needed to identify the node to use for commenting #}
+    <input type="hidden" name="type" value="{{type}}">
+    <input type="hidden" name="service" value="{{service}}">
+    <input type="hidden" name="node" value="{{node}}">
+{% endmacro %}
+
+{% macro textbox(service, node, action='', placeholder='',
+                 submit_label=_("Send"), type="textbox",
+                 class='', ta_class='') %}
+    {# generic content area for comments/blog posts/etc.
+       Only a body by default, but new elements can be
+       added by using this macro with call #}
+    {% set extra_content = caller() if caller is defined else '' %}
+    {% call form.form(action=action, class="textbox " + class) %}
+        {{ head(service, node, type) }}
+        {{ extra_content }}
+        <article class="media">
+          {% if identities is defined and own_jid is defined %}
+              {% if avatar is defined %}
+                  <div class="media-left">
+                      {{ avatar.avatar(own_jid.userhost()) }}
+                  </div>
+              {% endif %}
+          {% endif %}
+          <div class="media-content">
+            {{ field.textarea("body", placeholder=placeholder, required=True) }}
+            <nav class="level">
+              <div class="level-left">
+                <div class="level-item">
+                   {{ field.submit() }}
+                </div>
+              </div>
+            </nav>
+          </div>
+        </article>
+        {# {{ field.textarea("body", placeholder=placeholder, required=True,
+                          class=ta_class) }}
+        {{ submit(label=submit_label) }} #}
+    {% endcall %}
+{% endmacro %}
+
+{% macro blog_text(service, node, action='', placeholder=_("Your comment")) %}
+    {{ textbox(service, node, action=action, placeholder=placeholder, type="comment") }}
+{% endmacro %}
+
+{% macro comment(service, node, action='', placeholder=_("Your comment"), class='box--medium') %}
+    {{ textbox(service, node, action=action, placeholder=placeholder, type="comment", class=class) }}
+{% endmacro %}
+
+{% macro comment_or_login(service, node, action='', placeholder=none) %}
+    {# show comment form a a message asking to log in
+       login is checked using profile #}
+    {% if profile %}
+        {% if placeholder is none %}
+            {{ comment(service, node, action) }}
+        {% else %}
+            {{ comment(service, node, action, placeholder=placeholder) }}
+        {% endif %}
+    {% else %}
+        <div class="container">
+            <article class="message">
+                <div class="message-body">
+                    <p>{% trans %}You are not logged. You need to log in to comment.{% endtrans %}</p>
+                    {% if login_url is defined %}
+                        <p class="log_in_url">
+                        {% trans link_start=('<a href="',login_url,'">')|join|safe, link_end='</a>'|safe %}
+                        To log in {{link_start}}follow this link{{link_end}}
+                        {% endtrans %}
+                        </p>
+                    {% endif %}
+                </div>
+            </article>
+        </div>
+    {% endif %}
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/input/xmlui.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,276 @@
+{% import 'input/field.html' as field %}
+
+{# generate methods #}
+
+{% macro generate_container(cont, config) %}
+    {% if cont.type == 'vertical' %}
+        {{ vertical_container(cont, config) }}
+    {% elif cont.type == 'pairs' %}
+        {{ pairs_container(cont, config) }}
+    {% elif cont.type == 'label' %}
+        {{ label_container(cont, config) }}
+    {% endif %}
+{% endmacro %}
+
+{% macro generate_widget(wid, config, id=none) %}
+    {% if wid.type == 'text' %}
+        {{ text_widget(wid, config, id=id) }}
+    {% elif wid.type == 'label' %}
+        {{ label_widget(wid, config) }}
+    {% elif wid.type == 'string' %}
+        {{ string_widget(wid, config, id=id) }}
+    {% elif wid.type == 'jid' %}
+        {# TODO: proper JID widget #}
+        {{ string_widget(wid, config, id=id) }}
+    {% elif wid.type == 'textbox' %}
+        {{ textbox_widget(wid, config, id=id) }}
+    {% elif wid.type == 'xhtmlbox' %}
+        {{ xhtmlbox_widget(wid, config, id=id) }}
+    {% elif wid.type == 'list' %}
+        {{ list_widget(wid, config, id=id) }}
+    {% endif %}
+{% endmacro %}
+
+{% macro generate_children(cont, config) %}
+    {% for child in cont.children %}
+        {% if child.category == 'container' %}
+            {{ generate_container(child, config) }}
+        {% else %}
+            {{ generate_widget(child, config) }}
+        {% endif %}
+    {% endfor %}
+
+{% endmacro %}
+
+{% macro generate(xmlui, form=true, filters=none, attributes=none, ignore=none) %}
+{# generate HTML from XMLUI
+    @param xmlui(template_xmlui.XMLUIPanel): xmlui to use
+    @param form(bool): if true will generate form elements
+    @param filters(dict,none): filters as expected by item_filter
+    @param attributes(dict,none): extra attributes to put on named widgets
+#}
+    {% set config = {
+        'form':form,
+        'filters':filters or {},
+        'attrs': attributes or {},
+        'ignore': ignore or [],
+        }
+    %}
+    {{ generate_container(xmlui.main_cont, config) }}
+{% endmacro %}
+
+{% macro generate_table(xmlui_items, fields, formatters, tr_class_fields, on_click) %}
+{# generate a HTML table from requested widgets names
+    @param xmlui_items(iterable[unicode]): list of xmlui to show (one per row)
+    @param fields(tuple[unicode,unicode]): fields to show (name, label)
+    @param formatters(dict): dictionary of templates to format values:
+        field_name => template
+        if no formatter is set (or None is used) for a field, it will be used unmodified.
+        current xmlui items will be set as "item" key
+    @param tr_class_fields(iterable[unicode]): name of fields to use as class
+        class will be "{name}_{value}" where name is field name, and value field value
+        all lowercase/stripped
+    @param on_click(data_objects.OnClick): thing to do when clicking on a row
+#}
+    {% if formatters is undefined %}
+        {% set formatters = {} %}
+    {% endif %}
+    {% if on_click is undefined %}
+        {% set on_click = {} %}
+    {% endif %}
+    <table>
+        <thead>
+            <tr>
+                {% for name,label in fields %}
+                    <th>{{ label }}</th>
+                {% endfor %}
+            </tr>
+        </thead>
+        <tbody>
+            {% for xmlui in xmlui_items %}
+                {% set link=on_click.formatUrl(item=xmlui.widget_value) if on_click.url else none %}
+                <tr {{ {'class': xmlui|xmlui_class(tr_class_fields)}|xmlattr }}>
+
+                    {% for name,label in fields %}
+                        <td {{ {'class': 'td_'+name}|xmlattr }}>
+                            {% for value in xmlui.widgets[name].values %}
+                                <a {{ {'href':link}|xmlattr }}>{{ value|adv_format(formatters.get(name),item=xmlui.widget_value) }}</a>
+                            {% endfor %}
+                        </td>
+                    {% endfor %}
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+{% endmacro %}
+
+
+
+
+{% macro generate_list(xmlui_items, fields, formatters, item_class_fields, field_class_map, on_click) %}
+{# generate a list of rendered XMLUI from requested widgets names
+    very similar to generate_table but generate a list instead of a tabme
+    @param xmlui_items(iterable[unicode]): list of xmlui to show
+    @param fields(tuple[unicode,unicode]): fields to show (name, label)
+    @param formatters(dict): dictionary of templates to format values:
+        field_name => template
+        if no formatter is set (or None is used) for a field, it will be used unmodified.
+        current xmlui items will be set as "item" key for the template
+    @param item_class_fields(iterable[unicode]): name of fields to use as class
+        class will be "{name}_{value}" where name is field name, and value field value
+        all lowercase/stripped
+    @param field_class_map(dict): dictionary of field name to classes to use
+    @param on_click(data_objects.OnClick): thing to do when clicking on a row
+#}
+    {% if formatters is undefined %}
+        {% set formatters = {} %}
+    {% endif %}
+    {% if on_click is undefined %}
+        {% set on_click = {} %}
+    {% endif %}
+    {% if field_class_map is undefined %}
+        {% set field_class_map = {} %}
+    {% endif %}
+    {% for xmlui in xmlui_items %}
+        {% set link=on_click.formatUrl(item=xmlui.widget_value) if on_click.url else none %}
+            <div class="media x-is-hoverable">
+                <div class="media-content">
+                    <a {{ {'class': 'has-text-black ' + xmlui|xmlui_class(item_class_fields),
+                        'href':link}|xmlattr }}>
+                        <div class="content">
+                            {% for name,label in fields %}
+                                <span {{ {'class': 'xmlui_field__'+name}|xmlattr }}>
+                                    {% for label in xmlui.widgets.get(name, {}).labels %}
+                                        <span {{ {'class': field_class_map.get(name)}|xmlattr }}>{{ label|adv_format(formatters.get(name),item=xmlui.widget_value) }}</span>
+                                    {% endfor %}
+                                </span>
+                            {% endfor %}
+                        </div>
+                    </a>
+                </div>
+            </div>
+    {% endfor %}
+{% endmacro %}
+
+
+
+
+
+{# containers #}
+
+{% macro vertical_container(cont, config) %}
+    <div class="xmlui_cont xmlui_cont_vertical">
+        {{ generate_children(cont, config) }}
+    </div>
+{% endmacro %}
+
+{% macro pairs_container(cont, config) %}
+    {# TODO: proper impelmentation (do the same as vertical container for now #}
+    <div class="xmlui_cont xmlui_cont_vertical">
+        {{ generate_children(cont, config) }}
+    </div>
+{% endmacro %}
+
+{% macro label_container(cont, config) %}
+    <div class="xmlui_cont xmlui_cont_vertical">
+        {% for child in cont.children %}
+            {% if loop.index is odd %}
+                {# label #}
+                {% if child.for_name not in config.ignore %}
+                    <div class="field">
+                    {% if child.type == 'label' %}
+                        {% set for_ = ('wid_' + (child.for_name or child.name or '_noname'))|next_gidx %}
+                        {{ label_widget(child, config, for=for_) }}
+                    {% endif %}
+                {% endif %}
+            {% else %}
+                {# widget #}
+                {% if child.name not in config.ignore %}
+                    {% set id = ('wid_' + (child.name or '_noname'))|cur_gidx %}
+                    {{ generate_widget(child, config, id=id) }}
+                    </div>
+                {% endif %}
+            {% endif %}
+        {% endfor %}
+    </div>
+{% endmacro %}
+
+
+{# widgets #}
+
+{% macro text_widget(wid, config, id=none) %}
+    {% if config.form %}
+        <input class="input is-static xmlui_widget xmlui_text" type="text" {{ {'id':id, 'value': wid|item_filter(config.filters)|default('\u00A0',true)}|xmlattr }}>
+    {% else %}
+        <p class="xmlui_widget xmlui_text" {{ {'id':id}|xmlattr }}>
+            {{- wid|item_filter(config.filters)|default('\u00A0',true) -}}
+        </p>
+    {% endif %}
+{% endmacro%}
+
+{% macro label_widget(wid, config, for=none) %}
+    {% if config.form %}
+        <label class="label xmlui_widget xmlui_label" {{ {'for':for}|xmlattr }}>
+            {{wid|item_filter(config.filters)}}
+        </label>
+    {% else %}
+        <span class="label xmlui_widget xmlui_label" {{ {'id':none if not for else 'label_%s'|format(for)}|xmlattr }}>{{wid|item_filter(config.filters)}}</span>
+    {% endif %}
+{% endmacro%}
+
+{% macro string_widget(wid, config, id=none) %}
+    {% if config.form %}
+        <input class="input xmlui_widget xmlui_string" type="text" {{ {'name':wid.name, 'id':id, 'value':wid|item_filter(config.filters)}|dict_ext(config.attrs, wid.name)|xmlattr }}
+         {{ "readonly" if wid.read_only }} >
+    {% else %}
+        <p class="content">
+            {{- wid|item_filter(config.filters) -}}
+        </p>
+    {% endif %}
+{% endmacro%}
+
+{% macro textbox_widget(wid, config, id=none) %}
+    {% if config.form %}
+        <textarea class="textarea xmlui_widget xmlui_textbox" rows="10" cols="50" {{ {'name':wid.name, 'id':id}|dict_ext(config.attrs, wid.name)|xmlattr }}>
+            {{- wid|item_filter(config.filters) -}}
+        </textarea>
+    {% else %}
+        <p class="content xmlui_widget xmlui_textbox" {{ {'id':id}|xmlattr }}>
+            {{- wid|item_filter(config.filters) -}}
+        </p>
+    {% endif %}
+{% endmacro%}
+
+{% macro xhtmlbox_widget(wid, config, id=none) %}
+    {% if config.form and not wid.read_only %}
+        <textarea class="textarea xmlui_widget xmlui_xhtmlbox" rows="10" cols="50" {{ {'name':wid.name, 'id':id}|dict_ext(config.attrs, wid.name)|xmlattr }}>
+            {{- wid|item_filter(config.filters) -}}
+        </textarea>
+    {% else %}
+        <div class="content xmlui_widget xmlui_xhtmlbox" {{ {'id':id}|xmlattr }}>
+            {{- wid|item_filter(config.filters) -}}
+        </div>
+    {% endif %}
+{% endmacro%}
+
+{% macro list_widget(wid, config, id=none) %}
+    {% if config.form %}
+        <div class="select">
+            <select class="xmlui_widget xmlui_list" {{ {'name':wid.name, 'id':id}|dict_ext(config.attrs, wid.name)|xmlattr }}>
+                {% for value,label in wid.options %}
+                    <option {{ {'value':value}|xmlattr }} {{ 'selected' if value in wid.selected }}>
+                        {{- label -}}
+                    </option>
+                {% endfor %}
+            </select>
+        </div>
+    {% else %}
+        <div class="content xmlui_widget xmlui_list" {{ {'id':id}|xmlattr }}>
+            {% for value,label in wid.items %}
+                <span class="xmlui_list_item value_{{value|attr_escape}}">
+                    {{- label -}}
+                </span>
+            {% endfor %}
+        </div>
+    {% endif %}
+{% endmacro%}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/login/logged.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,29 @@
+{% set post_confirm_message=_("You have been logged correctly") %}
+{% if not embedded %}{% extends 'base/base.html' %}{% endif %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+{% block title %}{{C.APP_NAME}}{% endblock %}
+
+{% block confirm_message %}
+    {% trans %}You have been logged correctly{% endtrans %}
+{% endblock confirm_message %}
+
+{% block body %}
+    <section class="section" id='logged'>
+        <div class='message'>
+            <div class="message-body">
+                {% if guest_session %}
+                    <p>{% trans %}You are logged as a guest{% endtrans %}</p>
+                {% else %}
+                    <p>{% trans name='<span class="logged_profile">'|safe + profile + '</span>'|safe %}You are logged under the account {{name}} {% endtrans %}</p>
+                {% endif %}
+                <p>{% trans session_started='<span class="logged_time">'|safe + session_started|date_fmt('relative') + '</span>'|safe %}You logged {{session_started}}{% endtrans %}</p>
+            </div>
+        </div>
+        {% call form.form() %}
+        {{ field.meta('type', 'disconnect') }}
+        {{ field.submit(_("Disconnect")) }}
+        {% endcall %}
+    </section>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/login/login.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,65 @@
+{% if not embedded %}{% extends 'base/base.html' %}{% endif %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+{% block title %}{{C.APP_NAME}} login{% endblock %}
+
+{% block body %}
+{{ icon_defs('group') }}
+<section class="section">
+    <div class="columns is-vcentered is-centered">
+        <div class="column is-narrow is-hidden-touch">
+            <img src="{{media_path}}libervia/register_left.png">
+        </div>
+        <div class="column is-narrow">
+            {% block login_right_top %}{% endblock %}
+            {% if login_error is defined %}
+                {# error messages displayed in case of failing attempt to login #}
+                <article class="message is-danger">
+                  <div class="message-body">
+                    {% block login_error_message %}
+                        {% if login_error == S_C.PROFILE_AUTH_ERROR %}
+                            {%- trans %}Your login and/or password is incorrect. Please try again.{% endtrans -%}
+                        {% elif login_error == S_C.XMPP_AUTH_ERROR %}
+                            {%- trans %}Your XMPP account failed to connect. Did you enter the good password? If you have changed your XMPP password since your last connection on Libervia, please use another SàT frontend to update your profile.{% endtrans -%}
+                        {% elif login_error == S_C.NO_REPLY %}
+                            {%- trans %}Did not received a reply (the timeout expired or the connection is broken).{% endtrans -%}
+                        {% else %}
+                            {%- trans %}An unknown error occurred, please contact your service administrator.{% endtrans -%}
+                        {% endif %}
+                    {% endblock login_error_message %}
+                  </div>
+                </article>
+            {% endif %}
+
+            <div id="login_form">
+                {% block login_form %}
+                {% call form.form() %}
+                    {{ field.meta('type', 'login') }}
+                    {{ field.text("login", _("Login"),
+                                  required=true,
+                                  value=login,
+                                  icon_left="person",
+                                  )}}
+                                  {{ field.password("password", _("Password"),
+                                  required=not empty_password_allowed,
+                                  icon_left="key") }}
+                    {{ field.submit(_("Log in")) }}
+                {% endcall %}
+                {% endblock login_form %}
+            </div>
+        </div>
+    </div>
+</section>
+{% if register_url is defined %}
+    <section class="section">
+        <article class="message is-info">
+            <div class="message-body has-text-centered has-text-weight-bold">
+                <p>
+                    <a href="{{register_url}}">{% trans %}No account yet? Create a new one!{% endtrans %}</a>
+                </p>
+            </div>
+        </article>
+    </section>
+{% endif %}
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/login/register.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,54 @@
+{% extends 'login/login.html' %}
+
+{% block title %}{{C.APP_NAME}} register new account{% endblock %}
+
+{% block login_error_message %}
+{% if login_error == S_C.ALREADY_EXISTS %}
+    {%- trans %}This login already exists, please choose another one.{% endtrans -%}
+{% elif login_error == S_C.INVALID_CERTIFICATE %}
+    {%- trans %}The certificate of the server is invalid. Please contact your server administrator.{% endtrans -%}
+{% elif login_error == S_C.INVALID_INPUT %}
+    {%- trans %}The data you entered are not valid.{% endtrans -%}
+{% elif login_error == S_C.BAD_REQUEST %}
+    {%- trans %}Bad request, please contact your service administrator.{% endtrans -%}
+{% else %}
+    {%- trans %}An unknown error occurred, please contact your service administrator.{% endtrans -%}
+{% endif %}
+{% endblock login_error_message %}
+
+{% block login_right_top %}
+{% if login_url is defined %}
+    <div id="login_link">
+        <a href="{{login_url}}">
+            {%- trans %}Go to login page{% endtrans -%}
+        </a>
+    </div>
+{% endif %}
+{% endblock login_right_top %}
+
+{% block login_form %}
+{% call form.form(class='register') %}
+    {{ field.meta('type', 'register') }}
+    {{ field.text("login", _("Login"),
+                  required=true,
+                  pattern=S_C.REG_LOGIN_RE,
+                  title=_("Login must be lower case, with only plain letters (a-z), numbers (0-9) or underscore(_)"),
+                  value=login,
+                  icon_left="person",
+                  )}}
+    {{ field.email("email", _("Email"),
+                   required=true,
+                   value=email,
+                   icon_left="mail-filled",
+                   )}}
+    {{ field.password("password", _("Password"),
+                      required=true,
+                      minlength=S_C.PASSWORD_MIN_LENGTH,
+                      value=password,
+                      icon_left="key",
+                      )}}
+    {{ field.submit(_("Register new account")) }}
+{% endcall %}
+{% endblock login_form %}
+
+{% block login_right_bottom %}{% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/merge-request/create.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,13 @@
+{# creata a new ticket #}
+
+{% set category_menu = [('merge-requests', url_tickets_list)] %}
+{% extends 'base/base.html' %}
+{% block body %}
+    <section class="section">
+        <div class="message is-warning">
+            <div class="message-body">
+                {% trans %}It is not yet possible to create a merge request from inside Libervia, please use <pre>jp merge-request set</pre> for now. Merge requests welcome ;){% endtrans %}
+            </div>
+        </div>
+    </section>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/merge-request/discover.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,30 @@
+{% extends 'base/base.html' %}
+{% import 'components/block.html' as block with context %}
+{% import 'components/images.html' as images with context %}
+{% import 'components/avatar.html' as avatar with context %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+{% block body %}
+{{ icon_defs('merge') }}
+<section class="section">
+    <article class="message has-text-centered">
+        <div class="message-body">
+            {% trans %}
+            Please select a merge-requests handler
+            {% endtrans %}
+        </div>
+    </article>
+    {% if mr_handlers is defined %}
+        <div class="disco_tickets">
+            {{block.disco_icon_grid(mr_handlers, 'merge')}}
+        </div>
+    {% endif %}
+</section>
+<section class="section">
+    {% call form.form(class="form--single") %}
+        {{ field.text("jid", _("handler jid"), required=true)}}
+        {{ field.submit(_("Access")) }}
+    {% endcall %}
+</section>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/merge-request/edit.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,29 @@
+{# edit an existing ticket #}
+
+{% set category_menu = [('merge-requests', url_tickets_list),
+                        ('merge-request_new', url_tickets_new)] %}
+{% extends 'base/base.html' %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+{% import 'input/xmlui.html' as xmlui with context %}
+
+{% block body %}
+<div class='instructions instructions--border box box--medium'>
+    <p>
+        <strong>{% trans %}Note:{% endtrans %}</strong>
+        {% trans app_name=C.APP_NAME%}to modify content of the merge request, you'll have to use command line (with jp){% endtrans %}
+    </p>
+</div>
+<div class="create single ticket box box--medium">
+{% call form.form() %}
+    {{ xmlui.generate(new_ticket_xmlui,
+                      attributes = {'title': {'required': 'required',
+                                              'placeholder': _("Short description of your issue/request")},
+                                    'body': {'required': 'required',
+                                             'placeholder': _("Please describe your issue/request with as much details as possible. You can use Markdown syntax.")},
+                                    'labels': {'placeholder': _("You can enter one or several labels separated by commas")},
+                                    })}}
+    {{ field.submit(_("Modify ticket")) }}
+{% endcall %}
+</div>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/merge-request/item.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,131 @@
+{# display a single ticket
+
+    @variable item(xmlui_item): ticket to display
+    @variable comments(data_object.BlogItems): comments of the ticket
+    @variable comments_service(unicode): service for adding comments
+    @variable comments_node(unicode): node for adding comments
+#}
+
+{% set category_menu = [('merge-requests', url_tickets_list),
+                        ('merge-request_new', url_tickets_new)] %}
+{% if not embedded %}{% extends 'base/base.html' %}{% endif %}
+{% import 'input/xmlui.html' as xmlui with context %}
+{% import 'components/avatar.html' as avatar with context %}
+{% import 'blog/macros.html' as blog with context %}
+{% import 'input/textbox.html' as textbox with context %}
+
+{% block title %}{{item|adv_format('#{value.widget_value.id} {value.widget_value.title}') }}{% endblock %}
+
+{% block confirm_message %}
+    {% trans %}Your comment has been sent{% endtrans %}
+{% endblock confirm_message %}
+
+{% block body %}
+{{ icon_defs('pencil') }}
+<section class="section">
+    <div class="tab__container">
+        <div class="tabs">
+            <ul>
+                <li class="tab__btn is-active" onclick='tab_select(this, "tab_description", btn_clicked_cls="is-active")'>
+                    <a>
+                        {% trans %}Description{% endtrans %}
+                    </a>
+                </li>
+                <li class="tab__btn" onclick='tab_select(this, "tab_patches", btn_clicked_cls="is-active")'>
+                    <a>
+                        {% trans %}Patches{% endtrans %}
+                    </a>
+                </li>
+            </ul>
+        </div>
+
+        <div class="tab__page state_clicked" id="tab_description">
+            <div class="columns">
+                <div class="column has-background-white">
+                    <div id="{{ item.widget_value['id'] }}" class="media has-padding-1">
+                        {% if identities is defined %}
+                            {% if avatar is defined %}
+                                <figure class="media-left">
+                                    {{ avatar.avatar(item.widget_value['publisher'].bare) }}
+                                </figure>
+                            {% endif %}
+                        {% endif %}
+                        <div class="media-content">
+                            <div class="content">
+                                <h4 class="title is-4">{{item.widget_value['title']}}</h1>
+                                {{ item.widget_value['body'] }}
+
+                            </div>
+                            {% if comments is defined %}
+                                {{ blog.show_items(comments|reverse, expanded=true) }}
+                            {% endif %}
+                            {% if comments_node is defined %}
+                                <div class="comment_post">
+                                    {{- textbox.comment_or_login(service=comments_service, node=comments_node) -}}
+                                </div>
+                            {% endif %}
+                        </div>
+                        {% if url_ticket_edit is defined %}
+                            <div class="media-right">
+                                <a href="{{url_ticket_edit}}">
+                                    {{ icon('pencil', cls='icon is-64x64') }}
+                                </a>
+                            </div>
+                        {% endif %}
+                    </div>
+                </div>
+                <div class="column is-one-quarter has-background-light">
+                    {{
+                    xmlui.generate(
+                        item,
+                        form=false,
+                        filters={
+                            'created': {
+                                'filters': ['date_fmt'],
+                                'filters_args':[{'fmt': 'short'}]
+                            },
+                            'updated': {
+                                'filters': ['date_fmt'],
+                                'filters_args':[{'fmt': 'short'}]
+                            },
+                            'body': {
+                                'filters': ['urlize'],
+                                'filters_args':[{
+                                    'nofollow': True,
+                                    'rel': 'noopener noreferrer'
+                                }]
+                            }
+                        },
+                        ignore=['publisher', 'title', 'body', 'comments_uri'],
+                    )
+                    }}
+                </div>
+            </div>
+        </div>
+
+        <div class="tab__page" id="tab_patches">
+            {% for patch in patches %}
+                <div class="card">
+                    <div class="card-content has-background-primary">
+                        <p class="commit_msg">{{patch.commit_msg}}</p>
+                    </div>
+                    <div class="card-footer has-vpadding-6">
+                        <div class="level">
+                            <div class="level-left">
+                                <div class="level-item">
+                                    <span class="author has-text-weight-bold">{{patch.author}}</span>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="box has-margin-top-1">
+                    <div class="diff">
+                        {{- patch.diff|highlight('diff') -}}
+                    </div>
+                </div>
+            {% endfor %}
+        </div>
+    </div>
+</section>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/photo/album.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,44 @@
+{% extends 'base/base.html' %}
+{% import 'components/avatar.html' as avatar with context %}
+{% import 'input/textbox.html' as textbox with context %}
+{% import 'blog/macros.html' as blog with context %}
+
+{% block body %}
+{{ icon_defs('comment-empty') }}
+<div class="columns album is-multiline has-margin-top-1">
+    {% for file in files_data %}
+        {% if file.type == C.FILE_TYPE_FILE %}
+            <div class="column is-4">
+                <div class="card x-is-hoverable">
+                    <div class="card-image is-photo-thumbnail-container is-flex has-items-centered has-background-light">
+                        <a href="{{file.url}}" class="is-wrapping">
+                            <img class="is-photo-thumbnail" src="{{file.thumb_url}}" alt="{{file.name}}">
+                        </a>
+                    </div>
+                    <div class="card-content" onclick="clicked_mh_fix('{{'comments_panel'|next_gidx}}')">
+                        <div class="level">
+                            <div class="level-left"></div>
+                            <div class="level-right">
+                                <div class="level-item is-size-7">
+                                    {% if file.comments_url is defined %}
+                                        {% if file.comments_count %}
+                                            <span class='comments__count'>{{file.comments_count}} </span>
+                                        {% endif %}
+                                        {{ icon('comment-empty', cls='icon is-small') }}
+                                    {% endif %}
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div id='{{'comments_panel'|cur_gidx}}' class="has-margin-top-1 panel-drawer">
+                    {{ blog.show_items(file.comments, expanded=true, dates_fmt='relative') }}
+                    <div class="comment_post has-margin-top-1">
+                        {{- textbox.comment_or_login(service=file.comments_service, node=file.comments_node) -}}
+                    </div>
+                </div>
+            </div>
+        {% endif %}
+    {% endfor %}
+</div>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/photo/discover.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,32 @@
+{% extends 'base/base.html' %}
+{% import 'components/block.html' as block with context %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+{% block body %}
+{{ icon_defs('file-image') }}
+<section class="section">
+    <article class="message has-text-centered">
+        <div class="message-body">
+            {% trans %}
+            Photo discovery is not implemented yet, however you can enter a jid below to find its albums
+            {% endtrans %}
+        </div>
+    </article>
+</section>
+{% if interests is defined %}
+    <p class="message--note">
+        {% trans nb_interests=interests|length%}
+            You have currently {{nb_interests}} album in your personal list
+        {% pluralize %}
+            You have currently {{nb_interests}} albums in your personal list
+        {% endtrans %}
+    </p>
+
+    {{block.interests_grid(interests, 'file-image')}}
+{% endif %}
+{% call form.form(class="form--single") %}
+    {{ field.text("jid", _("device full jid"), required=true)}}
+    {{ field.submit(_("Access")) }}
+{% endcall %}
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/settings.json	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,3 @@
+{
+    "css_default_fallback": false
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/static/highlight.css	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,1 @@
+../../default/static/highlight.css
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/static/styles.css	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,161 @@
+:root {
+  --photo-height: 280px;
+  --photo-height--poster: 500px;
+}
+
+.has-whitespace-pre {
+    white-space: pre;
+}
+
+.is-avatar {
+    height: 64px;
+    width: 64px;
+    border-radius: 50%;
+    border: 1px solid #bbb;
+    background-color: #eee;
+    overflow: hidden;
+}
+
+.is-photo-thumbnail {
+    max-height: var(--photo-height);
+    max-width: 100%;
+}
+
+.is-photo-thumbnail-container {
+    height: var(--photo-height);
+}
+
+.has-items-centered {
+    align-items: center;
+    justify-content: center;
+}
+
+.is-poster {
+    height: var(--photo-height--poster) !important;
+}
+
+.is-poster>.is-photo-thumbnail {
+    max-height: var(--photo-height--poster) !important;
+}
+
+.has-items-vcentered {
+    align-items: center;
+}
+
+
+.x-is-hoverable:hover {
+    background-color: #eee !important;
+}
+
+.is-chat-message {
+    margin: 0.5rem 0 0 !important;
+    padding: 0 !important;
+    border: 0 !important;
+}
+
+a.is-wrapping {
+    line-height: 0;
+}
+
+.has-margin-top-1 {
+    margin-top: 1rem;
+}
+
+.has-vmargin-1 {
+    margin: 0.5rem 0;
+}
+
+.has-padding-1 {
+    padding: 0.25rem;
+}
+
+.has-vpadding-1 {
+    padding: 0 0.25rem;
+}
+
+.has-vpadding-6 {
+    padding: 0 1.5rem;
+}
+
+.has-no-background {
+	box-shadow: none !important;
+    background-color: initial !important;
+}
+
+.pagination-disabled {
+    background-color: white;
+    border-color: #dbdbdb;
+    color: #b5b5b5;
+    cursor: auto;
+}
+
+.pagination-disabled:hover {
+    background-color: white;
+    border-color: #dbdbdb;
+    color: #b5b5b5;
+}
+
+.has-text-shortenable {
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+
+.panel-drawer {
+    /* A panel which is hidden by default but can be
+       opened when it's clicked */
+    max-height: 0;
+    opacity: 0;
+    overflow: hidden;
+    transition: max-height 0.5s, opacity 0.5s;
+}
+
+.panel-drawer.state_clicked {
+    opacity: 1;
+}
+
+/********
+ * code *
+ ********/
+
+.highlight {
+    overflow: auto;
+}
+
+/**********
+ * blocks *
+ **********/
+
+.block_separator {
+    font-size: 1.4em;
+    display: flex;
+    margin: 1rem 0;
+}
+
+.block_separator__label {
+    display: inline-block;
+    margin: 0 0.2em;
+}
+
+.block_separator__line {
+    height: 1px;
+    background: #ccc;
+    flex: 1;
+    margin-top: 0.7em;
+}
+
+/********
+ * tabs *
+ *******/
+
+#tab_guests {
+    overflow: auto;
+}
+
+.tab__page {
+    display: None;
+}
+
+.tab__page.state_clicked {
+    display: block;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/static/styles_noscript.css	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,9 @@
+/**********
+ * Panels *
+ **********/
+
+/* drawer needs to be opened to have the content visible */
+.panel-drawer {
+    max-height: none;
+    opacity: 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/ticket/create.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,30 @@
+{# create a new ticket #}
+
+{% set category_menu = [('tickets_list', url_tickets_list)] %}
+{% extends 'base/base.html' %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+{% import 'input/xmlui.html' as xmlui with context %}
+
+{% block body %}
+    <div class="container">
+        <div class="message">
+            <div class="message-body">
+                <span class="content">{% trans app_name=C.APP_NAME%}This page allows you to report an issue or ask/suggest a new feature for {{app_name}}{% endtrans %}</span>
+            </div>
+        </div>
+
+        <div class="create single ticket box">
+            {% call form.form() %}
+            {{ xmlui.generate(new_ticket_xmlui,
+            attributes = {'title': {'required': 'required',
+            'placeholder': _("Short description of your issue/request")},
+            'body': {'required': 'required',
+            'placeholder': _("Please describe your issue/request with as much details as possible. You can use Markdown syntax.")},
+            'labels': {'placeholder': _("You can enter one or several labels separated by commas")},
+            })}}
+            {{ field.submit(_("Create ticket"), class="has-margin-top-1") }}
+            {% endcall %}
+        </div>
+    </div>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/ticket/discover.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,30 @@
+{% extends 'base/base.html' %}
+{% import 'components/block.html' as block with context %}
+{% import 'components/images.html' as images with context %}
+{% import 'components/avatar.html' as avatar with context %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+
+{% block body %}
+{{ icon_defs('clipboard') }}
+<section class="section">
+    <article class="message has-text-centered">
+      <div class="message-body">
+        {% trans %}
+        Please select a tickets tracker
+        {% endtrans %}
+      </div>
+    </article>
+    {% if tickets_trackers is defined %}
+        <div class="disco_tickets">
+            {{block.disco_icon_grid(tickets_trackers, 'clipboard')}}
+        </div>
+    {% endif %}
+</section>
+<section class="section">
+    {% call form.form(class="form--single") %}
+        {{ field.text("jid", _("tickets tracker jid"), required=true)}}
+        {{ field.submit(_("Access")) }}
+    {% endcall %}
+</section>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/ticket/edit.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,22 @@
+{# edit an existing ticket #}
+
+{% set category_menu = [('tickets_list', url_tickets_list)] %}
+{% extends 'base/base.html' %}
+{% import 'input/form.html' as form with context %}
+{% import 'input/field.html' as field with context %}
+{% import 'input/xmlui.html' as xmlui with context %}
+
+{% block body %}
+<div class="box">
+{% call form.form() %}
+    {{ xmlui.generate(new_ticket_xmlui,
+                      attributes = {'title': {'required': 'required',
+                                              'placeholder': _("Short description of your issue/request")},
+                                    'body': {'required': 'required',
+                                             'placeholder': _("Please describe your issue/request with as much details as possible. You can use Markdown syntax.")},
+                                    'labels': {'placeholder': _("You can enter one or several labels separated by commas")},
+                                    })}}
+    {{ field.submit(_("Modify ticket"), class="has-margin-top-1") }}
+{% endcall %}
+</div>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/ticket/item.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,80 @@
+{# display a single ticket
+
+    @variable item(xmlui_item): ticket to display
+    @variable comments(data_object.BlogItems): comments of the ticket
+    @variable comments_service(unicode): service for adding comments
+    @variable comments_node(unicode): node for adding comments
+#}
+
+{% set category_menu = [('tickets', url_tickets_list),
+                        ('ticket_new', url_tickets_new),
+                        ] %}
+{% if not embedded %}{% extends 'base/base.html' %}{% endif %}
+{% import 'input/xmlui.html' as xmlui with context %}
+{% import 'components/avatar.html' as avatar with context %}
+{% import 'blog/macros.html' as blog with context %}
+{% import 'input/textbox.html' as textbox with context %}
+
+{% block title %}{{item|adv_format('#{value.widget_value.id} {value.widget_value.title}') }}{% endblock %}
+
+{% block confirm_message %}
+    {% trans %}Your comment has been sent{% endtrans %}
+{% endblock confirm_message %}
+
+{% block body %}
+{{ icon_defs('pencil') }}
+<div class="columns has-margin-top-1">
+    <div class="column has-background-white">
+        <div id="{{ item.widget_value['id'] }}" class="media has-padding-1">
+            {% if identities is defined %}
+                {% if avatar is defined %}
+                    <figure class="media-left">
+                        {{ avatar.avatar(item.widget_value['publisher'].bare) }}
+                    </figure>
+                {% endif %}
+            {% endif %}
+            <div class="media-content">
+                <div class="content">
+                    <h4 class="title is-4">{{item.widget_value['title']}}</h1>
+                    {{ item.widget_value['body'] }}
+
+                </div>
+                {% if comments is defined %}
+                    {{ blog.show_items(comments|reverse, expanded=true) }}
+                {% endif %}
+                {% if comments_node is defined %}
+                    <div class="comment_post">
+                        {{- textbox.comment_or_login(service=comments_service, node=comments_node) -}}
+                    </div>
+                {% endif %}
+            </div>
+            {% if url_ticket_edit is defined %}
+                <div class="media-right">
+                    <a href="{{url_ticket_edit}}">
+                        {{ icon('pencil', cls='icon is-64x64') }}
+                    </a>
+                </div>
+            {% endif %}
+        </div>
+    </div>
+    <div class="column is-one-quarter has-background-light">
+        {{
+        xmlui.generate(
+            item,
+            form=false,
+            filters={
+                'created': {
+                    'filters': ['date_fmt'],
+                    'filters_args':[{'fmt': 'short'}]
+                },
+                'updated': {
+                    'filters': ['date_fmt'],
+                    'filters_args':[{'fmt': 'short'}]
+                },
+            },
+            ignore=['publisher', 'title', 'body', 'comments_uri'],
+        )
+        }}
+    </div>
+</div>
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/ticket/overview.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,38 @@
+{# display the list of tickets #}
+
+{% set category_menu = [('ticket_new', url_tickets_new)] %}
+{% extends 'base/base.html' %}
+{% import 'input/xmlui.html' as xmlui with context %}
+{% import 'input/navigation.html' as navigation with context %}
+
+{% block body %}
+<section class="section has-background-white">
+    <div class="content has-items-centered is-flex">
+        {{ component.action_button(url_tickets_new) }}
+    </div>
+    <div id="tickets" class="container has-background-white has-padding-1">
+        {{ xmlui.generate_list(
+              tickets,
+              (
+                  ('title', _('Title')),
+                  ('labels', _('Labels')),
+                  ('id', _('Id')),
+                  ('author', _('Author')),
+              ),
+              {
+                  'id': '\n#{value}',
+                  'author': _('by {value}'),
+              },
+               item_class_fields=['status', 'priority', 'severity'],
+               field_class_map={
+                   'title': 'has-text-weight-bold',
+                   'labels': 'tag is-rounded x-is-hoverable',
+                   'id': 'has-text-grey-light is-size-7 has-whitespace-pre',
+                   'author': 'is-size-7'
+               },
+               on_click=on_ticket_click)
+        }}
+    </div>
+</section>
+{{ navigation.prev_next(_("previous page"), _("next page")) }}
+{% endblock body %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_templates/templates/bulma/ticket/tickets.html	Tue May 19 00:02:34 2020 +0200
@@ -0,0 +1,12 @@
+{% extends 'base/base.html' %}
+{% import 'input/xmlui.html' as xmlui with context %}
+
+{% block body %}
+<div id="tickets">
+    {% for ticket in tickets %}
+        <div class="ticket_full">
+            {{ xmlui.generate(ticket) }}
+        </div>
+    {% endfor %}
+</div>
+{% endblock body %}
--- a/sat_templates/templates/default/static/common.js	Sun Apr 26 22:06:07 2020 +0200
+++ b/sat_templates/templates/default/static/common.js	Tue May 19 00:02:34 2020 +0200
@@ -65,22 +65,23 @@
     );
 }
 
-function tab_select(tab_btn_elt, tab_page_id) {
+function tab_select(tab_btn_elt, tab_page_id, btn_clicked_cls='state_clicked', tab_clicked_cls='state_clicked') {
+
     for (let elt of document.getElementsByClassName("tab__btn")) {
         if (elt === tab_btn_elt) {
-            elt.classList.add('state_clicked');
+            elt.classList.add(btn_clicked_cls);
         }
         else {
-            elt.classList.remove('state_clicked');
+            elt.classList.remove(btn_clicked_cls);
         }
     }
     let tab_page_elt = document.getElementById(tab_page_id);
     for (let elt of document.getElementsByClassName("tab__page")) {
         if (elt === tab_page_elt) {
-            elt.classList.add('state_clicked');
+            elt.classList.add(tab_clicked_cls);
         }
         else {
-            elt.classList.remove('state_clicked');
+            elt.classList.remove(tab_clicked_cls);
         }
     }
 }