roundabout,
created on Wednesday, 13 March 2024, 12:39:08 (1710333548),
received on Saturday, 30 March 2024, 13:10:52 (1711804252)
Author identity: vlad <vlad.muntoiu@gmail.com>
36b70dcbd348c0936f9fc1fbc74b8a06409795ad
app.py
@@ -20,6 +20,10 @@ from flask_migrate import Migrate
from PIL import Image from flask_httpauth import HTTPBasicAuth import config from flask_babel import Babel, gettext, ngettext _ = gettext n_ = gettextapp = flask.Flask(__name__) app.config.from_mapping(
@@ -35,9 +39,11 @@ auth = HTTPBasicAuth()
app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI app.config["SECRET_KEY"] = config.DB_PASSWORD app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False db = SQLAlchemy(app) bcrypt = Bcrypt(app) migrate = Migrate(app, db) from models import * from misc_utils import *
@@ -47,6 +53,14 @@ import celery_tasks
from celery import Celery, Task import celery_integration def get_locale(): return flask.request.accept_languages.best_match(["en", "ro"]) babel = Babel(app, locale_selector=get_locale) worker = celery_integration.init_celery_app(app) repositories = flask.Blueprint("repository", __name__, template_folder="templates/repository/")
@@ -84,7 +98,7 @@ def about():
@app.route("/help/") def help_index(): return flask.render_template("help.html")return flask.render_template("help.html", faqs=config.faqs)@app.route("/settings/", methods=["GET", "POST"])
@@ -108,7 +122,7 @@ def settings():
db.session.commit() flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success")flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>" + _("Settings saved")), category="success")return flask.redirect(f"/{flask.session.get('username')}", code=303)
@@ -145,15 +159,15 @@ def login():
if user and bcrypt.check_password_hash(user.password_hashed, password): flask.session["username"] = user.username flask.flash( Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"),Markup("<iconify-icon icon='mdi:account'></iconify-icon>" + _("Successfully logged in as {username}").format(username=username)),category="success") return flask.redirect("/", code=303) elif not user: flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"),flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>" + _("User not found")),category="alert") return flask.render_template("login.html") else: flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"),flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>" + _("Invalid password")),category="error") return flask.render_template("login.html") if "signup" in flask.request.form:
@@ -166,14 +180,14 @@ def login():
if not only_chars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): flask.flash(Markup( "<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"),_("Usernames may only contain Latin alphabet, numbers, '-' and '_'")),category="error") return flask.render_template("login.html") if username in config.RESERVED_NAMES: flask.flash( Markup( f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"),"<iconify-icon icon='mdi:account-error'></iconify-icon>" + _("Sorry, {username} is a system path").format(username=username)),category="error") return flask.render_template("login.html")
@@ -181,12 +195,12 @@ def login():
if user_check: flask.flash( Markup( f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"),"<iconify-icon icon='mdi:account-error'></iconify-icon>" + _("The username {username} is taken").format(username=username)),category="error") return flask.render_template("login.html") if password2 != password: flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"),flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>" + _("Make sure the passwords match")),category="error") return flask.render_template("login.html")
@@ -195,7 +209,7 @@ def login():
db.session.commit() flask.session["username"] = user.username flask.flash(Markup( f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"),"<iconify-icon icon='mdi:account'></iconify-icon>" + _("Successfully created and logged in as {username}").format(username=username)),category="success") notification = Notification({"type": "welcome"})
@@ -203,7 +217,6 @@ def login():
db.session.commit() result = celery_tasks.send_notification.delay(notification.id, [username], 1) flask.flash(f"Sending notification in task {result.id}", "success")return flask.redirect("/", code=303)
@@ -220,7 +233,7 @@ def new_repo():
if not only_chars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): flask.flash(Markup( "<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"),"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Repository names may only contain Latin alphabet, numbers, '-' and '_'")),category="error") return flask.render_template("new-repo.html")
@@ -234,7 +247,7 @@ def new_repo():
subprocess.run(["git", "init", repo.name], cwd=os.path.join(config.REPOS_PATH, flask.session.get("username"))) flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"),flask.flash(Markup(_("Successfully created repository {name}").format(name=name)),category="success") return flask.redirect(repo.route, code=303)
@@ -242,7 +255,7 @@ def new_repo():
@app.route("/logout") def logout(): flask.session.clear() flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged out"), category="info")flask.flash(Markup("<iconify-icon icon='mdi:account'></iconify-icon>" + _("Successfully logged out")), category="info")return flask.redirect("/", code=303)
@@ -286,7 +299,6 @@ def user_profile(username):
db.session.commit() result = celery_tasks.send_notification.delay(notification.id, [username], 1) flask.flash(f"Sending notification in task {result.id}", "success")db.session.commit() return flask.redirect("?", code=303)
@@ -955,14 +967,14 @@ def repository_prs(username, repository):
if head not in head_repo.branches or base not in base_repo.branches: flask.flash(Markup( "<iconify-icon icon='mdi:error'></iconify-icon>Bad branch name"),"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")),category="error") return flask.redirect(".", 303) head_data = db.session.get(Repo, head_route) if not head_data.visibility: flask.flash(Markup( "<iconify-icon icon='mdi:error'></iconify-icon>Head can't be restricted"),"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Head can't be restricted")),category="error") return flask.redirect(".", 303)
babel.cfg
@@ -0,0 +1,5 @@
[python: **.py] [jinja2: **/templates/**] extensions = jinja2.ext.autoescape,jinja2.ext.with_ [jinja2: **/email_templates/**] extensions = jinja2.ext.autoescape,jinja2.ext.with_
config.py
@@ -1,5 +1,6 @@
import os from dotenv import load_dotenv load_dotenv("secrets.env") DB_PASSWORD: str = os.environ.get("DB_PASSWORD")
@@ -163,3 +164,162 @@ To adjust your email notification preferences, log in to your account in a brows
""" www_protocol = f"http{'s' if suggest_https else ''}" faqs = """ <h1>FAQs and rules</h1> <dl> <dt><h2>How do I add my project?</h2></dt> <dd> <p> It's easy. Click the sign-up button, then click Create in the corner, give it a name, and you're all set. </p> </dd> <dt><h2>Do I need to have an account?</h2></dt> <dd> <p> No, using the service is allowed without registering. However, to post your own material, as well as to contribute to other projects, you need an account to identify you. </p> </dd> <dt><h2>Do you collect personal information?</h2></dt> <dd> <p> Not at all. We do not log analytics or actions, and all you need to make an account is a username (which can be fictional) and a password. </p> </dd> <dt><h2>Who is the service targeted at?</h2></dt> <dd> <p> The service is primarily targeted at enthusiasts (the modern version of <a href="//en.wikipedia.org/wiki/Hacker_culture">hackers</a> but not security breakers!), and while we will optimise for corporate use, large free software projects and even just personal file storage as well, as an enthusiast myself I try to make it better for my use. </p> </dd> <dt><h2>What projects do you host?</h2></dt> <dd> <p> Anything, as long as it's free software. <i>Free</i> means all users should have the <a href="https://www.gnu.org/philosophy/free-sw.html.en#four-freedoms">Four Freedoms</a>. It does not mean everyone has to be a user, so private projects are <strong>allowed</strong>, but if it's private you may not share it without giving these Four Freedoms. </p> <p> <b>In short — either you share freely, or you don't share.</b> </p> <p> Additionally, projects designed to operate with nonfree programs or that depend on nonfree libraries are generally allowed, but keep in mind they are useless in the Free World. However, it is advisable to share them, so others could change them to remove the nonfree dependency. It is recommended to add a disclaimer to the top of an important document, just so others won't get too excited about it and realise it's not for them. </p> <p> “Source-available” projects that don't respect the Four Freedoms are considered nonfree and banned from this site. </p> <p> Using this site as a discussion forum for nonfree software is also not allowed, unless it's for a collaborative effort to reverse-engineer it. Forums for more general topics, as well as free software, are allowed though. </p> <p> Moreover, all <em>public</em> material shared here must be appropriate for all ages and not contain any illegal, pornographic, sexual, political, terrorist or other inappropriate material. Mild swearing is allowed, but it must not be used to refer to sex. </p> <p> For private material though, we have no business as long as you're not abusing the site by hosting illegal content or overloading the server. </p> <p> Nonfree <em>artistic, non-functional</em> works are also fine, but due to the nature of the service, the nonfree terms will not be enforced. </p> </dd> <dt><h2>What does it cost?</h2></dt> <dd> <p> Currently, it is zero-price, besides being free software. However, we may start charging for some features in the future, but <strong>only for those that cost us</strong>, and not for the features we already have, assuming a normal usage. We will not put stupid limits such as three collaborators per repository for free accounts, as more doesn't cost us anything. </p> <p> Advertisements may also get added, but they will be only for logged-out users, and won't use JavaScript or animation, most importantly they won't track you either. </p> </dd> <dt><h2>What stack does this instance use?</h2></dt> <dd> <p> Currently, it's a Raspberry Pi 4 (8GB) running Debian, Nginx, Gunicorn and Python with Flask, on top of Postgres and Redis. </p> </dd> <dt><h2>Is email integration supported?</h2></dt> <dd> <p> Mailing lists aren't currently supported, but it would be a nice feature, so we're working on it. </p> </dd> <dt><h2>Is SSH supported?</h2></dt> <dd> <p> Not currently. While SSH is used in many workflows, we currently only support the Git Smart HTTP protocol including with SSL. It does everything Git SSH does. We encourage you to try it, and let us know if SSH is still important to you. </p> <p> We also do not support the <code>git://</code> or Dumb HTTP protocols as they are insecure and don't have any authentication. </p> <p> For credential memory, GitHub's <a href="https://github.com/git-ecosystem/git-credential-manager">Git Credential Manager</a> also works with our app without extra setup. </p> </dd> <dt><h2>Is some form of CI or workflow, or robots supported?</h2></dt> <dd> <p> No, but we are working on it. </p> </dd> <dt><h2>What licence does the app have?</h2></dt> <dd> <p> <a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3.0</a>, or any later version. </p> </dd> <dt><h2>Where does the name come from?</h2></dt> <dd> <p> The name is a play on the word <i>branch</i>, because a roundabout connects many branching roads. It also aligns with our goals to become federated and support collaboration across instances, which we'll call roundabouts. </p> <p> The name is to always be treated like a common noun, so it uses regular capitalisation, articles and plurals. </p> </dd> <dt><h2>What about that logo?</h2></dt> <dd> <p> That is a roundabout sign design commonly used in Europe; it may not be familiar if you live on the other side of the Atlantic. </p> <p> It can also take other meanings, with blue being associated with stability and purity, the arrows could also represent collaboration, a cycle of development and even code reuse and remixing due to the resemblance to the recycling logo. </p> <p> The logo is to be treated as public domain. </p> </dd> </dl> """
templates/about.html
@@ -1,123 +1,113 @@
{% extends "default.html" %} {% block title %} About{% trans %}About{% endtrans %}{% endblock %} {% block breadcrumbs %} <li><a href="/about">Information</a></li><li><a href="/about">{% trans %}Information{% endtrans %}</a></li>{% endblock %} {% block content %} <style>main {height: 100%;flex: 1 0 auto;<style> main { height: 100%; flex: 1 0 auto; } #about-page { background-image: linear-gradient(to bottom, #2196F3 0%, #1976D2 50%, #0D47A1 100%); top: 0; color: #ffffff; padding: 96px; align-items: center; justify-content: center; text-align: center; } #logo { position: absolute; width: 100%; height: 100%; background-image: url("/static/logo.svg"); background-size: contain; z-index: 1; } #logo-shadow { width: clamp(240px, 15vw, 512px); aspect-ratio: 1/1; position: relative; filter: drop-shadow(0 48px 64px #00000040) drop-shadow(0 16px 16px #00000020) drop-shadow(0 0 4px #00000030); } #logo.spinning { animation: spin 625ms cubic-bezier(0.16, 1, 0.3, 1); } @keyframes spin { 0% { transform: rotate(120deg);} #about-page {background-image: linear-gradient(to bottom, #2196F3 0%, #1976D2 50%, #0D47A1 100%);top: 0;color: #ffffff;padding: 96px;align-items: center;justify-content: center;text-align: center;}#logo {position: absolute;width: 100%;height: 100%;background-image: url("/static/logo.svg");background-size: contain;z-index: 1;}#logo-shadow {width: clamp(240px, 15vw, 512px);aspect-ratio: 1/1;position: relative;filter:drop-shadow(0 48px 64px #00000040)drop-shadow(0 16px 16px #00000020)drop-shadow(0 0 4px #00000030);}#logo.spinning {animation: spin 625ms cubic-bezier(0.16, 1, 0.3, 1);}@keyframes spin {0% {transform: rotate(120deg);}100% {transform: rotate(0deg);}100% { transform: rotate(0deg);} }#tagline {font-style: italic;font-size: 1.25em;}#tagline { font-style: italic; font-size: 1.25em; }footer {display: none;}footer { display: none; }.navrail {display: none;}</style><x-vbox id="about-page"><div id="logo-shadow"><div id="logo" onmousedown="waitForAnimation(this, 'spinning');"></div></div><h1 class="headline"><b>Roundabout</b> alpha testing</h1><span id="tagline">Repository hosting for everyone.</span><x-frame style="--width: 768px; --height: auto; text-align: initial;" id="infos"><article class="card"><section class="card-main"><h2>Licensing</h2><p>Roundabout is free software available under the GNU AGPLv3. You are free touse, study, modify and/or distribute both the server and client programs, and tohost your own instance.</p><h5 style="display: inline;">Copyright 2023, Roundabout contributors</h5><p>This program is free software: you can redistribute it and/or modifyit under the terms of the GNU Affero General Public License as published bythe Free Software Foundation, either version 3 of the License, or(at your option) any later version.</p><p>This program is distributed in the hope that it will be useful,but <b>without any warranty</b>; without even the implied warranty of<b>merchantability</b> or <b>fitness for a particular purpose</b>. See theGNU General Public License for more details.</p><p>You should have received a copy of the GNU General Public Licensealong with this program. If not, see<a href="https://www.gnu.org/licenses/">the GNU website</a>.</p><a href="https://www.gnu.org/licenses/agpl-3.0.html">Click to view a copy of the GNU AGPL</a></section></article><article class="card"><section class="card-main"><h2>Server information</h2><ul><li><b>Operating System:</b> {{ platform.platform() }}</li></ul></section></article></x-frame></x-vbox><script>function waitForAnimation(element, className) {element.classList.add(className);.navrail { display: none; } </style> <x-vbox id="about-page"> <div id="logo-shadow"> <div id="logo" onmousedown="waitForAnimation(this, 'spinning');"></div> </div> <h1 class="headline"><b>{% trans %}Roundabout{% endtrans %}</b> {% trans %}alpha testing{% endtrans %}</h1> <span id="tagline">{% trans %}Repository hosting for everyone.{% endtrans %}</span> <x-frame style="--width: 768px; --height: auto; text-align: initial;" id="infos"> <article class="card"> <section class="card-main"> <h2>{% trans %}Licensing{% endtrans %}</h2> <p> {% trans %}Roundabout is free software available under the GNU AGPLv3. You are free to use, study, modify and/or distribute both the server and client programs, and to host your own instance.{% endtrans %} </p> <h5 style="display: inline;">{% trans %}Copyright 2023, Roundabout contributors{% endtrans %}</h5> <p> {% trans %}This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.{% endtrans %} </p> <p> {% trans %}This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. See the GNU General Public License for more details.{% endtrans %} </p> <p> {% trans %}You should have received a copy of the GNU General Public License along with this program. If not, see the GNU website.{% endtrans %} <a href="https://www.gnu.org/licenses/">the GNU website</a>. </p> <a href="https://www.gnu.org/licenses/agpl-3.0.html">{% trans %}Click to view a copy of the GNU AGPL{% endtrans %}</a> </section> </article> <article class="card"> <section class="card-main"> <h2>{% trans %}Server information{% endtrans %}</h2> <ul> <li><b>{% trans %}Operating System:{% endtrans %}</b> {{ platform.platform() }}</li> </ul> </section> </article> </x-frame> </x-vbox> <script> function waitForAnimation(element, className) { element.classList.add(className);const stop = () => {element.classList.remove(className);element.removeEventListener("animationend", stop);};const stop = () => { element.classList.remove(className); element.removeEventListener("animationend", stop); };element.addEventListener("animationend", stop);}</script>{% endblock %}element.addEventListener("animationend", stop); } </script> {% endblock %}
templates/default.html
@@ -1,156 +1,156 @@
<!DOCTYPE html> <html lang="en"> <head>{% block head %}<link rel="stylesheet" href="/static/style.css"><title>{% block title %}Roundabout{% endblock %}</title><link rel="apple-touch-icon" sizes="512x512" href="/static/apple-touch-icon.png"><link rel="manifest" href="/static/site.webmanifest"><link rel="mask-icon" href="/static/safari-pinned-tab.svg" color="#3f51b5"><link rel="shortcut icon" href="/static/favicon.ico"><meta name="apple-mobile-web-app-title" content="Roundabout"><meta name="application-name" content="Roundabout"><meta name="theme-color" content="#37474f"><link rel="shortcut icon" href="/static/logo.svg"><script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script><meta name="viewport" content="width=device-width, initial-scale=1" />{% endblock %}</head><body><dialog id="sidenav" class="sheet-left"><x-frame style="--width: 320px; --margin: 0;"><article class="card"><section class="card-main"><nav class="sidenav"><ul>{% if logged_in_user %}<li><a href="/{{ logged_in_user }}"><img src="/info/{{ logged_in_user }}/avatar" class="avatar" style="width: 1em; height: 1em;">{{ logged_in_user }}</a></li><li><a href="/notifications"><iconify-icon icon="ic:baseline-inbox" data-badge="{{ unread }}"></iconify-icon>Notifications</a></li><li><a href="/newrepo"><iconify-icon icon="mdi:folder-plus"></iconify-icon>Create repository</a></li><li><a href="/favourites"><iconify-icon icon="mdi:star"></iconify-icon>Your favourites</a></li><li><a href="/settings"><iconify-icon icon="mdi:cog"></iconify-icon>Settings</a></li><li><a href="/logout"><iconify-icon icon="mdi:logout"></iconify-icon>Log out</a></li>{% else %}<li><a href="/accounts"><iconify-icon icon="mdi:account"></iconify-icon>Log in or sign up</a></li>{% endif %}</ul></nav></section></article></x-frame></dialog>{% block dialogs %}{% endblock %}<x-vbox class="nopad" style="align-items: stretch; height: 100vh;"><header><nav id="global-nav" class="breadcrumbs"><a href="javascript:document.getElementById('sidenav').showModal();" id="sidenav-trigger"><iconify-icon icon="mdi:menu"></iconify-icon></a><head> {% block head %} <link rel="stylesheet" href="/static/style.css"> <title>{% block title %}{% trans %}Roundabout{% endtrans %}{% endblock %}</title> <link rel="apple-touch-icon" sizes="512x512" href="/static/apple-touch-icon.png"> <link rel="manifest" href="/static/site.webmanifest"> <link rel="mask-icon" href="/static/safari-pinned-tab.svg" color="#3f51b5"> <link rel="shortcut icon" href="/static/favicon.ico"> <meta name="apple-mobile-web-app-title" content="Roundabout"> <meta name="application-name" content="Roundabout"> <meta name="theme-color" content="#37474f"> <link rel="shortcut icon" href="/static/logo.svg"> <script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script> <meta name="viewport" content="width=device-width, initial-scale=1" /> {% endblock %} </head> <body> <dialog id="sidenav" class="sheet-left"> <x-frame style="--width: 320px; --margin: 0;"> <article class="card"> <section class="card-main"> <nav class="sidenav"><ul> <li><a href="/">roundabout</a></li>{% block breadcrumbs %}{% endblock %}</ul><div class="flexible-space" id="navbar-separator"></div><x-buttonbox style="align-items: center; gap: 12px;">{% if logged_in_user %} <a href="/{{ logged_in_user }}"><x-hbox class="box-center" style="--gap-box: 1ch;"><img src="/info/{{ logged_in_user }}/avatar" class="avatar" style="width: 1em; height: 1em;">{{ logged_in_user }}</x-hbox></a><a href="/notifications"><li><a href="/{{ logged_in_user }}"> <img src="/info/{{ logged_in_user }}/avatar" class="avatar" style="width: 1em; height: 1em;"> {{ logged_in_user }} </a></li> <li><a href="/notifications"><iconify-icon icon="ic:baseline-inbox" data-badge="{{ unread }}"></iconify-icon> </a><a href="/newrepo"><iconify-icon icon="mdi:folder-plus" title="Create repository"></iconify-icon></a><a href="/favourites"><iconify-icon icon="mdi:star" title="Favourites"></iconify-icon></a><a href="/settings"><iconify-icon icon="mdi:cog" title="User settings"></iconify-icon></a><a href="/logout"><iconify-icon icon="mdi:logout" title="Log out"></iconify-icon></a>{% trans %}Notifications{% endtrans %} </a></li> <li><a href="/newrepo"> <iconify-icon icon="mdi:folder-plus"></iconify-icon> {% trans %}Create repository{% endtrans %} </a></li> <li><a href="/favourites"> <iconify-icon icon="mdi:star"></iconify-icon> {% trans %}Your favourites{% endtrans %} </a></li> <li><a href="/settings"> <iconify-icon icon="mdi:cog"></iconify-icon> {% trans %}Settings{% endtrans %} </a></li> <li><a href="/logout"> <iconify-icon icon="mdi:logout"></iconify-icon> {% trans %}Log out{% endtrans %} </a></li>{% else %} <a href="/accounts">Log in or sign up</a><li><a href="/accounts"> <iconify-icon icon="mdi:account"></iconify-icon> {% trans %}Log in or sign up{% endtrans %} </a></li>{% endif %} </x-buttonbox></ul></nav> {% if self.nav() | trim %}<nav id="repo-nav" class="navbar" style="max-height: 100%; flex: 0 0 var(--height-navbar);">{% block nav %}{% endblock %}</nav></section> </article> </x-frame> </dialog> {% block dialogs %} {% endblock %} <x-vbox class="nopad" style="align-items: stretch; height: 100vh;"> <header> <nav id="global-nav" class="breadcrumbs"> <a href="javascript:document.getElementById('sidenav').showModal();" id="sidenav-trigger"> <iconify-icon icon="mdi:menu"></iconify-icon> </a> <ul> <li><a href="/">{% trans %}roundabout{% endtrans %}</a></li> {% block breadcrumbs %}{% endblock %} </ul> <div class="flexible-space" id="navbar-separator"></div> <x-buttonbox style="align-items: center; gap: 12px;"> {% if logged_in_user %} <a href="/{{ logged_in_user }}"> <x-hbox class="box-center" style="--gap-box: 1ch;"> <img src="/info/{{ logged_in_user }}/avatar" class="avatar" style="width: 1em; height: 1em;"> {{ logged_in_user }} </x-hbox> </a> <a href="/notifications"> <iconify-icon icon="ic:baseline-inbox" data-badge="{{ unread }}"></iconify-icon> </a> <a href="/newrepo"> <iconify-icon icon="mdi:folder-plus" title="{% trans %}Create repository{% endtrans %}"></iconify-icon> </a> <a href="/favourites"> <iconify-icon icon="mdi:star" title="{% trans %}Favourites{% endtrans %}"></iconify-icon> </a> <a href="/settings"> <iconify-icon icon="mdi:cog" title="{% trans %}User settings{% endtrans %}"></iconify-icon> </a> <a href="/logout"> <iconify-icon icon="mdi:logout" title="{% trans %}Log out{% endtrans %}"></iconify-icon> </a> {% else %} <a href="/accounts">{% trans %}Log in or sign up{% endtrans %}</a>{% endif %} </header><main>{% block content %}{% endblock %}</main><footer>{{ Markup(config.footer) }}</footer></x-vbox>{% with messages = get_flashed_messages(with_categories=true) %}<ol class="toast-container">{% for category, message in messages %}<listyle="{% if category %}background-color:{% if category == 'error' %}var(--color-error){% elif category == 'alert' %}var(--color-alert){% elif category == 'info' %}var(--color-info){% elif category == 'success' %}var(--color-success){% endif %};color:{% if category == 'error' %}var(--color-error-text){% elif category == 'alert' %}var(--color-alert-text){% elif category == 'info' %}var(--color-info-text){% elif category == 'success' %}var(--color-success-text){% endif %};{% endif %}">{% if category | split | first == "task" %}{{ message }}{% else %}{{ message }}{% endif %}<x-buttonbox><button class="button-flat" onclick="removeToast()" style="color: inherit !important;">Close</button></x-buttonbox></li>{% endfor %}</ol>{% endwith %}</body><script src="/static/ripples.js"></script><script src="/static/efficient-ui/dialogs.js"></script><script src="/static/efficient-ui/toasts.js"></script>{% block scripts %}{% endblock %}</html></x-buttonbox> </nav> {% if self.nav() | trim %} <nav id="repo-nav" class="navbar" style="max-height: 100%; flex: 0 0 var(--height-navbar);"> {% block nav %}{% endblock %} </nav> {% endif %} </header> <main> {% block content %} {% endblock %} </main> <footer> {{ Markup(config.footer) }} </footer> </x-vbox> {% with messages = get_flashed_messages(with_categories=true) %} <ol class="toast-container"> {% for category, message in messages %} <li style=" {% if category %} background-color: {% if category == 'error' %}var(--color-error) {% elif category == 'alert' %}var(--color-alert) {% elif category == 'info' %}var(--color-info) {% elif category == 'success' %}var(--color-success) {% endif %}; color: {% if category == 'error' %}var(--color-error-text) {% elif category == 'alert' %}var(--color-alert-text) {% elif category == 'info' %}var(--color-info-text) {% elif category == 'success' %}var(--color-success-text) {% endif %}; {% endif %}" > {% if category | split | first == "task" %} {{ message }} {% else %} {{ message }} {% endif %} <x-buttonbox> <button class="button-flat" onclick="removeToast()" style="color: inherit !important;">{% trans %}Close{% endtrans %}</button> </x-buttonbox> </li> {% endfor %} </ol> {% endwith %} <script src="/static/ripples.js"></script> <script src="/static/efficient-ui/dialogs.js"></script> <script src="/static/efficient-ui/toasts.js"></script> {% block scripts %} {% endblock %} </body> </html>
templates/empty.html
@@ -1,11 +1,11 @@
{% extends "error.html" %} {% block error %} Empty repository{% trans %}Empty repository{% endtrans %}{% endblock %} {% block heading %} This repository is empty{% trans %}This repository is empty{% endtrans %}{% endblock %} {% block text %} This repository is currently empty, push to <code>{{ remote }}</code>.{% trans remote=remote %}This repository is currently empty, push to <code>{{ remote }}</code>.{% endtrans %}{% endblock %} {% block icon %}mdi:folder{% endblock %}{% block icon %}mdi:folder{% endblock %}
templates/error.html
@@ -8,8 +8,8 @@
<h1>{% block heading %}{% endblock %}</h1> <p>{% block text %}{% endblock %}</p> <x-buttonbox> <button onclick="history.back();">go back</button><button onclick="/">go home</button><button onclick="history.back();">{% trans %}go back{% endtrans %}</button> <button onclick="/">{% trans %}go home{% endtrans %}</button></x-buttonbox> </x-vbox> {% endblock %}{% endblock %}
templates/favourites.html
@@ -1,22 +1,26 @@
{% extends "default.html" %} {% block title %} Favourites{% trans %}Favourites{% endtrans %}{% endblock %} {% block breadcrumbs %} <li><a href="/favourites">Favourites</a></li><li><a href="/favourites">{% trans %}Favourites{% endtrans %}</a></li>{% endblock %} {% block content %} <x-vbox> <x-frame style="--width: 896px;" class="flexible-space"> <x-vbox> {% for favourite in favourites %}<article class="card card-horizontal"><section class="card-main flexible-space"><h3><a href="{{ favourite.repo.route }}">{{ favourite.repo.owner.username }}/{{ favourite.repo.name }}</a></h3></section></article>{% endfor %}{% if favourites|length > 0 %} {% for favourite in favourites %} <article class="card card-horizontal"> <section class="card-main flexible-space"> <h3><a href="{{ favourite.repo.route }}">{{ favourite.repo.owner.username }}/{{ favourite.repo.name }}</a></h3> </section> </article> {% endfor %} {% else %} <p>{% trans %}When you add favourite repositories, you can manage them from here.{% endtrans %}</p> {% endif %}</x-vbox> </x-frame> </x-vbox> {% endblock %}{% endblock %}
templates/file-view.html
@@ -2,8 +2,8 @@
<h1 style="display: flex; align-items: center; text-overflow: ellipsis; overflow: hidden;" title="{{ basename }}"><iconify-icon icon="{{ icon }}"></iconify-icon> {{ basename }}</h1> <div class="flexible-space"></div> <x-buttonbox> <a href="{{ file }}" class="button">View raw</a><a href="{{ file }}" download class="button">Download</a><a href="{{ file }}" class="button">{% trans %}View raw{% endtrans %}</a> <a href="{{ file }}" download class="button">{% trans %}Download{% endtrans %}</a></x-buttonbox> </x-hbox> {{ mimetype }} • {{ size[0] }} {{ size[1] }}
@@ -55,12 +55,12 @@
<img src="{{ file }}"> {% elif mode == "audio" %} <audio controls src="{{ file }}" style="width: 100%;"> Your browser does not support HTML5 multimedia.<a href="{{ file }}">Download file</a>{% trans %}Your browser does not support HTML5 multimedia.{% endtrans %} <a href="{{ file }">{% trans %}Download file{% endtrans %}</a></audio> {% elif mode == "video" %} <video controls src="{{ file }}" style="width: 100%;"> Your browser does not support HTML5 multimedia.<a href="{{ file }}">Download file</a>{% trans %}Your browser does not support HTML5 multimedia.{% endtrans %} <a href="{{ file }">{% trans %}Download file{% endtrans %}</a></video> {% endif %}
templates/forbidden.html
@@ -1,11 +1,11 @@
{% extends "error.html" %} {% block error %} 403{% trans %}403{% endtrans %}{% endblock %} {% block heading %} 403 forbidden{% trans %}403 forbidden{% endtrans %}{% endblock %} {% block text %} You are not authorised to access this resource.{% trans %}You are not authorised to access this resource.{% endtrans %}{% endblock %} {% block icon %}mdi:key-alert{% endblock %}{% block icon %}mdi:key-alert{% endblock %}
templates/help.html
@@ -1,165 +1,10 @@
{% extends "home.html" %} {% block title %} FAQs{% trans %}FAQs{% endtrans %}{% endblock %} {% block content %} <x-frame style="--width: 768px;"> <h1>FAQs and rules</h1><dl><dt><h2>How do I add my project?</h2></dt><dd><p>It's easy. Click the sign-up button, then click Create in the corner, give it a name, and you're allset.</p></dd><dt><h2>Do I need to have an account?</h2></dt><dd><p>No, using the service is allowed without registering. However, to post your own material, as well asto contribute to other projects, you need an account to identify you.</p></dd><dt><h2>Do you collect personal information?</h2></dt><dd><p>Not at all. We do not log analytics or actions, and all you need to make an account is a username(which can be fictional) and a password.</p></dd><dt><h2>Who is the service targeted at?</h2></dt><dd><p>The service is primarily targeted at enthusiasts(the modern version of <a href="//en.wikipedia.org/wiki/Hacker_culture">hackers</a> but not securitybreakers!), and while we will optimise for corporate use, large free software projects and even justpersonal file storage as well, as an enthusiast myself I try to make it better for my use.</p></dd><dt><h2>What projects do you host?</h2></dt><dd><p>Anything, as long as it's free software. <i>Free</i> means all users should have the<a href="https://www.gnu.org/philosophy/free-sw.html.en#four-freedoms">Four Freedoms</a>.It does not mean everyone has to be a user, so private projects are <strong>allowed</strong>,but if it's private you may not share it without giving these Four Freedoms.</p><p><b>In short — either you share freely, or you don't share.</b></p><p>Additionally, projects designed to operate with nonfree programs or that depend on nonfree librariesare generally allowed, but keep in mind they are useless in the Free World. However, it is advisableto share them, so others could change them to remove the nonfree dependency. It is recommended toadd a disclaimer to the top of an important document, just so others won't get too excited about itand realise it's not for them.</p><p>“Source-available” projects that don't respect the Four Freedoms are considered nonfree and bannedfrom this site.</p><p>Using this site as a discussion forum for nonfree software is also not allowed, unless it's fora collaborative effort to reverse-engineer it. Forums for more general topics, as well as freesoftware, are allowed though.</p><p>Moreover, all <em>public</em> material shared here must be appropriate for all ages and not containany illegal, pornographic, sexual, political, terrorist or other inappropriate material. Mildswearing is allowed, but it must not be used to refer to sex.</p><p>For private material though, we have no business as long as you're not abusing the site by hostingillegal content or overloading the server.</p><p>Nonfree <em>artistic, non-functional</em> works are also fine, but due to the nature of the service, thenonfree terms will not be enforced.</p></dd><dt><h2>What does it cost?</h2></dt><dd><p>Currently, it is zero-price, besides being free software. However, we may start charging for somefeatures in the future, but <strong>only for those that cost us</strong>, and not for the featureswe already have, assuming a normal usage. We will not put stupid limits such as three collaboratorsper repository for free accounts, as more doesn't cost us anything.</p><p>Advertisements may also get added, but they will be only for logged-out users, and won't useJavaScript or animation, most importantly they won't track you either.</p></dd><dt><h2>What stack does this instance use?</h2></dt><dd><p>Currently, it's a Raspberry Pi 4 (8GB) running Debian, Nginx, Gunicorn and Python with Flask, on topof Postgres and Redis.</p></dd><dt><h2>Is email integration supported?</h2></dt><dd><p>Mailing lists aren't currently supported, but it would be a nice feature, so we're working on it.</p></dd><dt><h2>Is SSH supported?</h2></dt><dd><p>Not currently. While SSH is used in many workflows, we currently only support the Git Smart HTTPprotocol including with SSL. It does everything Git SSH does. We encourage you to try it, and let usknow if SSH is still important to you.</p><p>We also do not support the <code>git://</code> or Dumb HTTP protocols as they are insecure and don'thave any authentication.</p><p>For credential memory, GitHub's<a href="https://github.com/git-ecosystem/git-credential-manager">Git Credential Manager</a>also works with our app without extra setup.</p></dd><dt><h2>Is some form of CI or workflow, or robots supported?</h2></dt><dd><p>No, but we are working on it.</p></dd><dt><h2>What licence does the app have?</h2></dt><dd><p><a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3.0</a>, or any later version.</p></dd><dt><h2>Where does the name come from?</h2></dt><dd><p>The name is a play on the word <i>branch</i>, because a roundabout connects many branching roads.It also aligns with our goals to become federated and support collaboration across instances, whichwe'll call roundabouts.</p><p>The name is to always be treated like a common noun, so it uses regular capitalisation, articles andplurals.</p></dd><dt><h2>What about that logo?</h2></dt><dd><p>That is a roundabout sign design commonly used in Europe; it may not be familiar if you live on theother side of the Atlantic.</p><p>It can also take other meanings, with blue being associated with stability and purity, the arrowscould also represent collaboration, a cycle of development and even code reuse and remixing due tothe resemblance to the recycling logo.</p><p>The logo is to be treated as public domain.</p></dd></dl>{{ Markup(faqs) }}</x-frame> {% endblock %}
templates/home.html
@@ -1,6 +1,6 @@
{% extends "default.html" %} {% block title %} Home{% trans %}Home{% endtrans %}{% endblock %} {% block breadcrumbs %} {% endblock %}
templates/login.html
@@ -1,6 +1,6 @@
{% extends "default.html" %} {% block title %} Login{% trans %}Login{% endtrans %}{% endblock %} {% block breadcrumbs %} {% endblock %}
@@ -11,7 +11,6 @@
justify-content: center; } @media screen and (max-width: 640px) { #login-forms { flex-direction: column;
@@ -21,62 +20,62 @@
<x-frame style="--width: 384px;"> <x-hbox id="login-forms"> <x-vbox> <h2>Log in</h2><h2>{% trans %}Log in{% endtrans %}</h2><form method="post" id="login"> <input type="hidden" name="login" value="login"> <div class="card"> <section> <x-vbox> <x-vbox class="nopad"> <label for="login-username">Username</label><label for="login-username">{% trans %}Username{% endtrans %}</label><input id="login-username" name="username" required> </x-vbox> <x-vbox class="nopad"> <label for="login-password">Password</label><label for="login-password">{% trans %}Password{% endtrans %}</label><input id="login-password" name="password" type="password" required> </x-vbox> <button style="width: 100%;" id="login-submit" type="submit">Log in</button><button style="width: 100%;" id="login-submit" type="submit">{% trans %}Log in{% endtrans %}</button></x-vbox> </section> </div> </form> </x-vbox> <x-vbox> <h2>Sign up</h2><h2>{% trans %}Sign up{% endtrans %}</h2><form method="post" id="signup"> <input type="hidden" name="signup" value="signup"> <div class="card"> <section> <x-vbox> <x-vbox class="nopad"> <label for="signup-username">Wanted username</label><label for="signup-username">{% trans %}Wanted username{% endtrans %}</label><input id="signup-username" name="username" required> </x-vbox> <x-vbox class="nopad"> <label for="signup-password">Password</label><label for="signup-password">{% trans %}Password{% endtrans %}</label><input id="signup-password" name="password" type="password" required> </x-vbox> <x-vbox class="nopad"> <label for="signup-password-2">Repeat password</label><label for="signup-password-2">{% trans %}Repeat password{% endtrans %}</label><input id="signup-password-2" name="password2" type="password" required> </x-vbox> <x-vbox class="nopad"> <label for="signup-email">Email (recommended)</label><label for="signup-email">{% trans %}Email (recommended){% endtrans %}</label><input id="signup-email" name="email" type="email"> </x-vbox> <x-vbox class="nopad" style="display: none;"> <label for="signup-email-2">Repeat email (recommended)</label><label for="signup-email-2">{% trans %}Repeat email (recommended){% endtrans %}</label><input id="signup-email-2" name="email2" type="email"> </x-vbox> <x-vbox class="nopad"> <label for="signup-name">Friendly name (optional)</label><label for="signup-name">{% trans %}Friendly name (optional){% endtrans %}</label><input id="signup-name" name="name"> </x-vbox> <label> <input id="signup-terms" type="checkbox" name="tos" required> <span>I accept the <a href="/help/policies">policies listed here</a></span><span>{% trans %}I accept the{% endtrans %} <a href="/help/policies">{% trans %}policies listed here{% endtrans %}</a></span></label> <button style="width: 100%;" id="signup-submit" type="submit">Sign up</button><button style="width: 100%;" id="signup-submit" type="submit">{% trans %}Sign up{% endtrans %}</button></x-vbox> </section> </div>
templates/method-not-allowed.html
@@ -1,11 +1,11 @@
{% extends "error.html" %} {% block error %} 405{% trans %}405{% endtrans %}{% endblock %} {% block heading %} 405 method not allowed{% trans %}405 method not allowed{% endtrans %}{% endblock %} {% block text %} This resource is not intended to be accessed with the current method.{% trans %}This resource is not intended to be accessed with the current method.{% endtrans %}{% endblock %} {% block icon %}mdi:swap-horizontal-bold{% endblock %}{% block icon %}mdi:swap-horizontal-bold{% endblock %}
templates/new-repo.html
@@ -1,6 +1,6 @@
{% extends "default.html" %} {% block title %} Create repository{% trans %}Create repository{% endtrans %}{% endblock %} {% block breadcrumbs %} {% endblock %}
@@ -8,21 +8,21 @@
{% block content %} <x-frame style="--width: 384px;"> <x-vbox> <h1>Create repository</h1><h1>{% trans %}Create repository{% endtrans %}</h1><form method="post" id="create"> <div class="card"> <section> <x-vbox> <x-vbox class="nopad"> <label for="name">Name</label><label for="name">{% trans %}Name{% endtrans %}</label><input id="name" name="name" required> </x-vbox> <x-hbox> <label><input type="radio" name="visibility" value="2" checked>Public</label><label><input type="radio" name="visibility" value="1">Unlisted</label><label><input type="radio" name="visibility" value="0">Private</label><label><input type="radio" name="visibility" value="2" checked>{% trans %}Public{% endtrans %}</label> <label><input type="radio" name="visibility" value="1">{% trans %}Unlisted{% endtrans %}</label> <label><input type="radio" name="visibility" value="0">{% trans %}Private{% endtrans %}</label></x-hbox> <button style="width: 100%;" id="submit" type="submit">Create</button><button style="width: 100%;" id="submit" type="submit">{% trans %}Create{% endtrans %}</button></x-vbox> </section> </div>
templates/no-home.html
@@ -1,6 +1,6 @@
{% extends "default.html" %} {% block title %} Welcome{% trans %}Welcome{% endtrans %}{% endblock %} {% block breadcrumbs %} {% endblock %}
@@ -9,47 +9,47 @@
{% endblock %} {% block content %} <style>#home-banner {background-image: radial-gradient(circle, #2196F3 0%, #1976D2 50%, #0D47A1 100%);color: #ffffff;height: 320px;}<style> #home-banner { background-image: radial-gradient(circle, #2196F3 0%, #1976D2 50%, #0D47A1 100%); color: #ffffff; height: 320px; }#home-banner .button {background-color: var(--color-accent);color: var(--color-accent-text);border: none;}#home-banner .button { background-color: var(--color-accent); color: var(--color-accent-text); border: none; }#home-banner a {color: inherit;}</style><x-hbox id="home-banner" style="justify-content: center; align-items: stretch;"><x-frame class="inner" style="--width: 768px; padding: 24px; margin: 0; justify-content: center; display: flex; flex-direction: column;"><x-vbox style="justify-content: center; align-items: stretch;"><div><h1 class="headline">The roundabout to all your code</h1><p>Git repository hosting, made simple. Powered by free software.</p></div>#home-banner a { color: inherit; } </style> <x-hbox id="home-banner" style="justify-content: center; align-items: stretch;"> <x-frame class="inner" style="--width: 768px; padding: 24px; margin: 0; justify-content: center; display: flex; flex-direction: column;"> <x-vbox style="justify-content: center; align-items: stretch;"> <div> <h1 class="headline">{% trans %}The roundabout to all your code{% endtrans %}</h1> <p>{% trans %}Git repository hosting, made simple. Powered by free software.{% endtrans %}</p> </div><x-buttonbox class="box-center"><a class="button" href="/accounts">Log in or sign up</a><a class="button" href="/download">Download</a><p style="opacity: 0.75;">for setting up your own instance</p></x-buttonbox><x-buttonbox class="box-center"> <a class="button" href="/accounts">{% trans %}Log in or sign up{% endtrans %}</a> <a class="button" href="/download">{% trans %}Download{% endtrans %}</a> <p style="opacity: 0.75;">{% trans %}for setting up your own instance{% endtrans %}</p> </x-buttonbox><x-hbox><a href="/about">System Info</a><a href="/help">Help & FAQ</a><a href="mailto:{{ config.CONTACT_EMAIL }}">Contact Us</a></x-hbox></x-vbox></x-frame><x-frame style="--width: 384px; margin: 0;"><x-vbox style="justify-content: center; align-items: center; height: 100%; align-self: stretch;"></x-vbox></x-frame></x-hbox><x-hbox> <a href="/about">{% trans %}System Info{% endtrans %}</a> <a href="/help">{% trans %}Help & FAQ{% endtrans %}</a> <a href="mailto:{{ config.CONTACT_EMAIL }}">{% trans %}Contact Us{% endtrans %}</a> </x-hbox> </x-vbox> </x-frame> <x-frame style="--width: 384px; margin: 0;"> <x-vbox style="justify-content: center; align-items: center; height: 100%; align-self: stretch;"> </x-vbox> </x-frame> </x-hbox>{% endblock %}
templates/not-found.html
@@ -1,11 +1,11 @@
{% extends "error.html" %} {% block error %} 404{% trans %}404{% endtrans %}{% endblock %} {% block heading %} 404 not found{% trans %}404 not found{% endtrans %}{% endblock %} {% block text %} The resource you have requested is not available on this server.{% trans %}The resource you have requested is not available on this server.{% endtrans %}{% endblock %} {% block icon %}mdi:cloud-alert{% endblock %}{% block icon %}mdi:cloud-alert{% endblock %}
templates/notifications.html
@@ -1,22 +1,26 @@
{% extends "default.html" %} {% block title %} Notifications{% trans %}Notifications{% endtrans %}{% endblock %} {% block breadcrumbs %} <li><a href="/notifications">Notifications</a></li><li><a href="/notifications">{% trans %}Notifications{% endtrans %}</a></li>{% endblock %} {% block content %} <x-vbox> <x-frame style="--width: 896px;" class="flexible-space"> <x-vbox> {% for notification in notifications %}<article class="card"><section class="card-main">{{ notification.notification.json }}</section></article>{% endfor %}{% if notifications %} {% for notification in notifications %} <article class="card"> <section class="card-main"> {{ notification.notification.json }} </section> </article> {% endfor %} {% else %} <p>{% trans %}When you get notifications, they'll be shown here.{% endtrans %}</p> {% endif %}</x-vbox> </x-frame> </x-vbox> {% endblock %}{% endblock %}
templates/path-bar.html
@@ -1,11 +1,10 @@
<select id="branch-selection" style="flex: 0 1 auto;"> <option value="ref-{{ current }}" selected>{% if "tag:" in current %}tag:{% endif %}{{ current | replace("~", "/") | replace("tag:", " ") }}</option><option value="ref-{{ current }}" selected>{% if "tag:" in current %}{% trans %}tag:{% endtrans %} {% endif %}{{ current | replace("~", "/") | replace("tag:", " ") }}</option>{% for branch in branches %} {% if branch[0] != current | replace("~", "/") | replace("tag:", " ") %} <option value="ref-{% if branch[1] == 'tag' %}tag:{% endif %}{{ branch[0] | replace('/', '~') }}">{% if branch[1] == "tag" %}tag: {% endif %}{{ branch[0] }}</option><option value="ref-{% if branch[1] == 'tag' %}tag:{% endif %}{{ branch[0] | replace('/', '~') }}">{% if branch[1] == "tag" %}{% trans %}tag:{% endtrans %} {% endif %}{{ branch[0] }}</option>{% endif %} {% endfor %} <!--<option value="new">[CREATE NEW BRANCH]</option>--></select> <input id="repo-path-bar" value="{{ subpath }}"> <script>
templates/post.html
@@ -25,17 +25,17 @@
{% if logged_in_user %} <dd> <details class="reply-area"> <summary>Reply</summary><summary>{% trans %}Reply{% endtrans %}</summary><form method="POST" action="{{ post.number }}/reply"> <x-vbox> <x-vbox class="nopad"> <label for="{{ post.number }}-subject">Subject</label><input id="{{ post.number }}-subject" name="subject" value="Re: {{ post.subject }}" required><label for="{{ post.number }}-subject">{% trans %}Subject{% endtrans %}</label> <input id="{{ post.number }}-subject" name="subject" value="{% trans post_subject=post.subject %}Re: {{ post_subject }}{% endtrans %}" required></x-vbox> <textarea name="message" style="box-sizing: border-box;" rows="8" required></textarea> <x-buttonbox> <button type="submit">Submit</button><button type="submit">{% trans %}Submit{% endtrans %}</button></x-buttonbox> </x-vbox> </form>
@@ -51,7 +51,7 @@
{% endif %} {% elif post.children %} <dd> <a href="{{ post.number }}">Comments hidden; click to go deeper.</a><a href="{{ post.number }}">{% trans %}Comments hidden; click to go deeper.{% endtrans %}</a></dd> {% endif %} </dl>
templates/repo.html
@@ -12,7 +12,7 @@
<header class="card-top"> <div class="navbar navbar-mini"> <ul> <li><b>Clone</b></li><li><b>{% trans %}Clone{% endtrans %}</b></li></ul> <x-buttonbox class="dialog-tools"> <button class="button-flat button-neutral big-button" type="submit" form="clone-form"><iconify-icon icon="mdi:close"></iconify-icon></button>
@@ -22,7 +22,7 @@
<section class="card-main"> <form id="clone-form"> <x-vbox class="nopad"> <label for="clone-url">Clone over HTTP</label><label for="clone-url">{% trans %}Clone over HTTP{% endtrans %}</label><input id="clone-url" readonly value="{{ remote }}" size="80"> </x-vbox> </form>
@@ -34,37 +34,37 @@
<ul id="repo-tabs"> <li class="{% if active_page == 'tree' %}selected{% endif %}"> <a href="/{{ username }}/{{ repository }}/tree"> Files{% trans %}Files{% endtrans %}</a> </li> <li class="{% if active_page == 'branches' %}selected{% endif %}"> <a href="/{{ username }}/{{ repository }}/branches"> Branches{% trans %}Branches{% endtrans %}</a> </li>