Implement invite flow in the web portal

This allows us to translate the pages using the same tooling and
to have consistent theming.
This commit is contained in:
Jonas Schäfer
2021-01-25 17:00:38 +01:00
parent f84adb28ac
commit c1132ae975
23 changed files with 963 additions and 23 deletions

View File

@@ -216,9 +216,11 @@ def create_app() -> quart.Quart:
from .main import bp as main_bp
from .user import bp as user_bp
from .admin import bp as admin_bp
from .invite import bp as invite_bp
app.register_blueprint(main_bp)
app.register_blueprint(user_bp, url_prefix="/user")
app.register_blueprint(admin_bp, url_prefix="/admin")
app.register_blueprint(invite_bp, url_prefix="/invite")
return app

158
snikket_web/invite.py Normal file
View File

@@ -0,0 +1,158 @@
import pathlib
import typing
import urllib.parse
import aiohttp
import quart.flask_patch
from quart import (
Blueprint,
render_template,
redirect,
url_for,
session as http_session,
)
import wtforms
import flask_wtf
from flask_babel import lazy_gettext as _l
from .infra import client, selected_locale
bp = Blueprint("invite", __name__)
INVITE_SESSION_JID = "invite-session-jid"
# https://play.google.com/store/apps/details?id=org.snikket.android&referrer={uri|urlescape}&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1
def apple_store_badge() -> str:
locale = selected_locale()
filename = "{}.svg".format(locale)
static_path = pathlib.Path(__file__).parent / "static" / "img" / "apple"
if (static_path / filename).exists():
return url_for("static", filename="img/apple/{}".format(filename))
return url_for("static", filename="img/apple/en.svg")
@bp.context_processor
def context() -> typing.Mapping[str, typing.Any]:
return {
"apple_store_badge": apple_store_badge,
}
@bp.route("/<id_>")
async def view(id_: str) -> str:
try:
invite = await client.get_public_invite_by_id(id_)
except aiohttp.ClientResponseError as exc:
if exc.status == 404:
# invite expired
return await render_template("invite_invalid.html")
raise
play_store_url = (
"https://play.google.com/store/apps/details?" +
urllib.parse.urlencode(
(
("id", "org.snikket.android"),
("referrer", invite.xmpp_uri),
("pcampaignid",
"pcampaignidMKT-Other-global-all-co-prtnr-py-"
"PartBadge-Mar2515-1"),
),
)
)
apple_store_url = (
"https://apps.apple.com/us/app/tigase-messenger/id1153516838"
)
return await render_template(
"invite_view.html",
invite=invite,
play_store_url=play_store_url,
apple_store_url=apple_store_url,
invite_id=id_,
)
class RegisterForm(flask_wtf.FlaskForm): # type:ignore
localpart = wtforms.StringField(
_l("Username"),
)
password = wtforms.PasswordField(
_l("Password"),
)
password_confirm = wtforms.PasswordField(
_l("Confirm password"),
validators=[wtforms.validators.InputRequired(),
wtforms.validators.EqualTo(
"password",
_l("The passwords must match")
)]
)
action_register = wtforms.SubmitField(
_l("Create account")
)
@bp.route("/<id_>/register", methods=["GET", "POST"])
async def register(id_: str) -> typing.Union[str, quart.Response]:
invite = await client.get_public_invite_by_id(id_)
form = RegisterForm()
if form.validate_on_submit():
# log the user in? show a guide? no idea.
try:
jid = await client.register_with_token(
username=form.localpart.data,
password=form.password.data,
token=id_,
)
except aiohttp.ClientResponseError as exc:
if exc.status == 409:
form.localpart.errors.append(
_l("That user name is already taken")
)
elif exc.status == 403:
form.localpart.errors.append(
_l("Registration was declined for unknown reasons")
)
elif exc.status == 400:
form.localpart.errors.append(
_l("The user name was not valid")
)
elif exc.status == 404:
return redirect(url_for(".view", id_=id_))
else:
raise
else:
http_session[INVITE_SESSION_JID] = jid
return redirect(url_for(".success"))
return await render_template(
"invite_register.html",
invite=invite,
form=form,
)
@bp.route("/success", methods=["GET", "POST"])
async def success() -> str:
return await render_template(
"invite_success.html",
jid=http_session.get(INVITE_SESSION_JID, ""),
)
@bp.route("/-")
async def index() -> quart.Response:
return redirect(url_for("index"))

View File

@@ -114,6 +114,22 @@ class AdminGroupInfo:
)
@dataclasses.dataclass(frozen=True)
class PublicInviteInfo:
inviter: typing.Optional[str]
xmpp_uri: str
@classmethod
def from_api_response(
cls,
data: typing.Mapping[str, typing.Any],
) -> "PublicInviteInfo":
return cls(
inviter=data.get("inviter") or None,
xmpp_uri=data["uri"],
)
class HTTPSessionManager:
def __init__(self, app_context_attribute: str):
self._app_context_attribute = app_context_attribute
@@ -258,6 +274,9 @@ class ProsodyClient:
def _admin_v1_endpoint(self, subpath: str) -> str:
return "{}/admin_api{}".format(self._endpoint_base, subpath)
def _public_v1_endpoint(self, subpath: str) -> str:
return "{}/register_api{}".format(self._endpoint_base, subpath)
async def _oauth2_bearer_token(self,
session: aiohttp.ClientSession,
jid: str,
@@ -1053,3 +1072,29 @@ class ProsodyClient:
return False
scopes = http_session[self.SESSION_CACHED_SCOPE].split()
return SCOPE_ADMIN in scopes
async def get_public_invite_by_id(self, id_: str) -> PublicInviteInfo:
async with self._plain_session as session:
async with session.get(self._public_v1_endpoint(
"/invite/{}".format(id_)
)) as resp:
resp.raise_for_status()
return PublicInviteInfo.from_api_response(await resp.json())
async def register_with_token(
self,
token: str,
username: str,
password: str,
) -> str:
payload = {
"username": username,
"password": password,
"token": token,
}
async with self._plain_session as session:
async with session.post(
self._public_v1_endpoint("/register"),
json=payload) as resp:
resp.raise_for_status()
return (await resp.json())["jid"]

View File

@@ -184,7 +184,7 @@ label.required:after {
padding: $w-s4;
}
p.form-desc.weak {
p.form-desc.weak, p.field-desc.weak {
color: $gray-300;
}
@@ -1116,7 +1116,7 @@ pre.guru-meditation {
}
}
p.form-desc.weak {
p.form-desc.weak, p.field-desc.weak {
color: $gray-700;
}
}

View File

@@ -0,0 +1,185 @@
@import "_theme.scss";
div.powered-by {
text-align: right;
line-height: 1.5;
> img {
height: 1.5em;
vertical-align: -0.2em;
margin-left: 0.2em;
}
}
div.modal {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
background: rgba(0, 0, 0, 0.5);
z-index: 1024;
width: 100%;
height: 100%;
> div {
padding: $w-l1;
margin-left: auto;
max-width: 40rem;
margin-right: auto;
> header {
display: flex;
flex-direction: row;
> span {
display: inline-block;
flex: 1 1 auto;
}
> a.button {
flex: 0 0 auto;
}
}
}
}
div.install-buttons {
display: flex;
flex-direction: column;
align-items: center;
ul {
display: flex;
flex-direction: row;
list-style-type: none;
margin: $w-l1 0;
padding: 0;
}
li {
margin: 0;
padding: 0;
}
}
img.apple {
height: $w-l2;
margin: $w-s2;
}
img.play {
height: $w-l3;
}
.tabbox {
display: flex;
flex-direction: column;
margin: $w-l1 0;
> nav.tabs {
display: flex;
flex-direction: row;
> a {
display: inline-block;
padding: $w-s2;
border-top-left-radius: $w-s4;
border-top-right-radius: $w-s4;
&, &:visited {
color: inherit;
text-decoration: underline;
text-decoration-color: $accent-500;
}
&:hover {
background: $accent-900;
border-color: $accent-800;
color: black;
}
&.active {
text-decoration: none;
background: linear-gradient(0deg, $accent-600, $accent-700);
color: $accent-200;
&:hover, &:focus {
background: linear-gradient(0deg, $accent-700, $accent-800);
}
&:active {
background: $accent-600;
}
}
}
}
> .tab-pane {
display: none;
padding: 0 $w-0;
background: $accent-900;
&.active {
display: block;
}
}
}
.qr {
margin: $w-l1 0;
display: flex;
flex-direction: row;
justify-content: center;
> img {
padding: $w-0;
background: white;
}
}
.float-right {
float: right;
}
#tutorial-scan {
width: $w-l5;
margin: $w-l1;
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24);
}
div.form.layout-expanded .lwrap {
display: flex;
flex-direction: row;
input.localpart-magic {
display: inline-block;
width: auto;
flex: 1 0 auto;
}
span {
display: inline-block;
flex: 0 0 auto;
background: $gray-900;
border: none;
border-bottom: $w-s4 solid $primary-500;
margin-bottom: -$w-s4;
padding: 0 $w-s3;
}
}
.fullwidth {
width: 100%;
}
#invite {
background: url('../img/invite-bg.jpg');
background-attachment: fixed;
background-size: cover;
}

View File

@@ -0,0 +1,46 @@
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
<title>Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917</title>
<g>
<g>
<g>
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
</g>
<g id="_Group_" data-name="&lt;Group&gt;">
<g id="_Group_2" data-name="&lt;Group&gt;">
<g id="_Group_3" data-name="&lt;Group&gt;">
<path id="_Path_" data-name="&lt;Path&gt;" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
<path id="_Path_2" data-name="&lt;Path&gt;" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
</g>
</g>
<g>
<path d="M42.30227,27.13965h-4.7334l-1.13672,3.35645H34.42727l4.4834-12.418h2.083l4.4834,12.418H43.438ZM38.0591,25.59082h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
<path d="M55.15969,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H48.4302v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.645,21.34766,55.15969,23.16406,55.15969,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30227,29.01563,53.24953,27.81934,53.24953,25.96973Z" style="fill: #fff"/>
<path d="M65.12453,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H58.395v1.50586h.03418A3.21162,3.21162,0,0,1,61.312,21.34766C63.60988,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26711,29.01563,63.21438,27.81934,63.21438,25.96973Z" style="fill: #fff"/>
<path d="M71.71047,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z" style="fill: #fff"/>
<path d="M86.065,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72609,30.6084,86.065,28.82617,86.065,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40039,1.16211-2.40039,3.10742c0,1.96191.89453,3.10645,2.40039,3.10645S92.76027,27.93164,92.76027,25.96973Z" style="fill: #fff"/>
<path d="M96.18606,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
<path d="M109.3843,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10207,25.13477Z" style="fill: #fff"/>
</g>
</g>
</g>
<g id="_Group_4" data-name="&lt;Group&gt;">
<g>
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z" style="fill: #fff"/>
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z" style="fill: #fff"/>
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z" style="fill: #fff"/>
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -27,6 +27,16 @@ licensed under the terms of the Apache 2.0 License -->
<g><rect fill="none" height="24" width="24" /><rect fill="none" height="24" width="24" /></g>
<g><path d="M10.3,7.7L10.3,7.7c-0.39,0.39-0.39,1.01,0,1.4l1.9,1.9H3c-0.55,0-1,0.45-1,1v0c0,0.55,0.45,1,1,1h9.2l-1.9,1.9 c-0.39,0.39-0.39,1.01,0,1.4l0,0c0.39,0.39,1.01,0.39,1.4,0l3.59-3.59c0.39-0.39,0.39-1.02,0-1.41L11.7,7.7 C11.31,7.31,10.69,7.31,10.3,7.7z M20,19h-7c-0.55,0-1,0.45-1,1v0c0,0.55,0.45,1,1,1h7c1.1,0,2-0.9,2-2V5c0-1.1-0.9-2-2-2h-7 c-0.55,0-1,0.45-1,1v0c0,0.55,0.45,1,1,1h7V19z" /></g>
</symbol>
<!-- from: action/exit_to_app/materialiconsround/24px.svg -->
<symbol id="icon-exit_to_app" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M10.79 16.29c.39.39 1.02.39 1.41 0l3.59-3.59c.39-.39.39-1.02 0-1.41L12.2 7.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L12.67 11H4c-.55 0-1 .45-1 1s.45 1 1 1h8.67l-1.88 1.88c-.39.39-.38 1.03 0 1.41zM19 3H5c-1.11 0-2 .9-2 2v3c0 .55.45 1 1 1s1-.45 1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1H6c-.55 0-1-.45-1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1v3c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
</symbol>
<!-- from: communication/qr_code/materialiconsround/24px.svg -->
<symbol id="icon-qrcode" viewBox="0 0 24 24">
<g><rect fill="none" height="24" width="24" /><rect fill="none" height="24" width="24" /></g>
<g><g><path d="M5,11h4c1.1,0,2-0.9,2-2V5c0-1.1-0.9-2-2-2H5C3.9,3,3,3.9,3,5v4C3,10.1,3.9,11,5,11z M5,5h4v4H5V5z" /><path d="M5,21h4c1.1,0,2-0.9,2-2v-4c0-1.1-0.9-2-2-2H5c-1.1,0-2,0.9-2,2v4C3,20.1,3.9,21,5,21z M5,15h4v4H5V15z" /><path d="M13,5v4c0,1.1,0.9,2,2,2h4c1.1,0,2-0.9,2-2V5c0-1.1-0.9-2-2-2h-4C13.9,3,13,3.9,13,5z M19,9h-4V5h4V9z" /><path d="M21,20.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28,0-0.5,0.22-0.5,0.5v1c0,0.28,0.22,0.5,0.5,0.5h1C20.78,21,21,20.78,21,20.5 z" /><path d="M13,13.5v1c0,0.28,0.22,0.5,0.5,0.5h1c0.28,0,0.5-0.22,0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1C13.22,13,13,13.22,13,13.5z" /><path d="M16.5,15h-1c-0.28,0-0.5,0.22-0.5,0.5v1c0,0.28,0.22,0.5,0.5,0.5h1c0.28,0,0.5-0.22,0.5-0.5v-1C17,15.22,16.78,15,16.5,15 z" /><path d="M13,17.5v1c0,0.28,0.22,0.5,0.5,0.5h1c0.28,0,0.5-0.22,0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1C13.22,17,13,17.22,13,17.5z" /><path d="M15.5,21h1c0.28,0,0.5-0.22,0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28,0-0.5,0.22-0.5,0.5v1C15,20.78,15.22,21,15.5,21 z" /><path d="M17.5,19h1c0.28,0,0.5-0.22,0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28,0-0.5,0.22-0.5,0.5v1C17,18.78,17.22,19,17.5,19 z" /><path d="M18.5,13h-1c-0.28,0-0.5,0.22-0.5,0.5v1c0,0.28,0.22,0.5,0.5,0.5h1c0.28,0,0.5-0.22,0.5-0.5v-1C19,13.22,18.78,13,18.5,13 z" /><path d="M19.5,17h1c0.28,0,0.5-0.22,0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28,0-0.5,0.22-0.5,0.5v1C19,16.78,19.22,17,19.5,17 z" /></g></g>
</symbol>
<!-- from: communication/vpn_key/materialiconsround/24px.svg -->
<symbol id="icon-passwd" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
@@ -92,4 +102,9 @@ licensed under the terms of the Apache 2.0 License -->
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M7 10H5V8c0-.55-.45-1-1-1s-1 .45-1 1v2H1c-.55 0-1 .45-1 1s.45 1 1 1h2v2c0 .55.45 1 1 1s1-.45 1-1v-2h2c.55 0 1-.45 1-1s-.45-1-1-1zm11 1c1.66 0 2.99-1.34 2.99-3S19.66 5 18 5c-.32 0-.63.05-.91.14.57.81.9 1.79.9 2.86s-.34 2.04-.9 2.86c.28.09.59.14.91.14zm-5 0c1.66 0 2.99-1.34 2.99-3S14.66 5 13 5s-3 1.34-3 3 1.34 3 3 3zm0 2c-2 0-6 1-6 3v1c0 .55.45 1 1 1h10c.55 0 1-.45 1-1v-1c0-2-4-3-6-3zm6.62.16c.83.73 1.38 1.66 1.38 2.84v1.5c0 .17-.02.34-.05.5h2.55c.28 0 .5-.22.5-.5V16c0-1.54-2.37-2.49-4.38-2.84z" />
</symbol>
<!-- from: navigation/close/materialiconsround/24px.svg -->
<symbol id="icon-close" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M18.3 5.71c-.39-.39-1.02-.39-1.41 0L12 10.59 7.11 5.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L10.59 12 5.7 16.89c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 13.41l4.89 4.89c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z" />
</symbol>
</defs></svg>

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,55 @@
var open_modal = function(a_el) {
var modal_id = "" + a_el.getAttribute("href").split("#")[1];
var modal_el = document.getElementById(modal_id);
modal_el.setAttribute("aria-modal", "true");
modal_el.removeAttribute("aria-hidden");
modal_el.style.setProperty("display", "block");
};
var close_modal = function(modal_el) {
modal_el.removeAttribute("aria-modal");
modal_el.setAttribute("aria-hidden", "true");
modal_el.style.setProperty("display", "none");
};
var find_tabbox_el = function(tab_content_el) {
var current = tab_content_el;
while (current) {
if (current.classList.contains("tabbox")) {
return current;
}
current = current.parentNode;
};
return null;
};
var clear_active_tab = function(tabbox_el) {
var nav_el = tabbox_el.firstChild;
var child = nav_el.firstChild;
while (child) {
child.setAttribute("aria-selected", "false");
child.classList.remove("active");
child = child.nextSibling;
}
var child = nav_el.nextSibling;
while (child) {
if (child.classList.contains("tab-pane")) {
child.classList.remove("active");
}
child = child.nextSibling;
}
};
var select_tab = function(tab_header_el) {
var tab_id = "" + tab_header_el.getAttribute("href").split("#")[1];
var tab_el = document.getElementById(tab_id);
clear_active_tab(find_tabbox_el(tab_el));
tab_el.classList.add("active");
tab_header_el.classList.add("active");
tab_header_el.setAttribute("aria-selected", "true");
};
var apply_qr_code = function(target_el) {
new QRCode(target_el, target_el.dataset.qrdata);
};

17
snikket_web/static/js/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
<footer>
<ul>
{#- -#}
<li>{% trans about_url=url_for('main.about') %}A <a href="{{ about_url }}">Snikket</a> service{% endtrans %}</li>
{#- -#}
</ul>
</footer>

View File

@@ -16,5 +16,5 @@
<meta name="msapplication-TileColor" content="#fbd308">
<meta name="theme-color" content="#fbd308">
</head>
<body{% if body_id | default(False) %} id="{{ body_id }}"{% endif %}{% if body_class | default(False) %} class="{{ body_class }}"{% endif %}>{% block body %}{% endblock %}</body>
<body{% if body_id | default(False) %} id="{{ body_id }}"{% endif %}{% if body_class | default(False) %} class="{{ body_class }}"{% endif %}{% if onload | default(False) %} onload="{{ onload }}"{% endif %}>{% block body %}{% endblock %}</body>
</html>

View File

@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% from "library.j2" import standard_button %}
{% block style %}
{{ super() }}
<link rel="stylesheet" type="text/css" href="{{ url_for("static", filename="css/invite.css") }}">
{% endblock %}
{% block body %}
<div id="mwrap"><main>{% block content %}{% endblock %}</main></div>
{%- include "_footer.html" -%}
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "invite.html" %}
{% block content %}
<div class="elevated box el-3">
<h1>{% trans site_name=config["SITE_NAME"] %}Invite to {{ site_name }}{% endtrans %}</h1>
<div class="powered-by">{% trans logo_url=url_for("static", filename="img/snikket-logo.svg") %}Powered by <img src="{{ logo_url }}" alt="Snikket">{% endtrans %}</div>
<div class="box alert">
<header>{% trans %}Invite expired{% endtrans %}</header>
<p>{% trans %}Sorry, it looks like this invite code has expired!{% endtrans %}</p>
</div>
<img alt="Sad person holding empty box" src="{{ url_for("static", filename="img/illus-empty.svg") }}" class="fullwidth">
</div>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends "invite.html" %}
{% set body_id = "invite" %}
{% from "library.j2" import form_button, render_errors %}
{% block head_lead %}
<title>{% trans site_name=config["SITE_NAME"] %}Register on {{ site_name }} | Snikket{% endtrans %}</title>
{% endblock %}
{% block content %}
<div class="elevated box el-3">
<h1>{% trans site_name=config["SITE_NAME"] %}Register on {{ site_name }}{% endtrans %}</h1>
<div class="powered-by">{% trans logo_url=url_for("static", filename="img/snikket-logo.svg") %}Powered by <img src="{{ logo_url }}" alt="Snikket">{% endtrans %}</div>
<p>{% trans site_name=config["SITE_NAME"] %}{{ site_name }} is using Snikket - a secure, privacy-friendly chat app.{% endtrans %}</p>
<h2>{% trans %}Create an account{% endtrans %}</h2>
<p>{% trans %}Creating an account will allow to communicate with other people using the Snikket app or compatible software. If you already have the app installed, we recommend that you continue the account creation process inside the app by clicking on the button below:{% endtrans %}</p>
<h3>{% trans %}App already installed?{% endtrans %}</h3>
{%- call standard_button("exit_to_app", invite.xmpp_uri, class="secondary") -%}
{% trans %}Open the app{% endtrans %}
{%- endcall -%}
<p class="weak">{% trans %}This button works only if you have the app installed already!{% endtrans %}</p>
<h3>{% trans %}Create an account online{% endtrans %}</h3>
<p>{% trans %}If you plan to use a legacy XMPP client, you can register an account online and enter your credentials into any XMPP-compatible software.{% endtrans %}</p>
<form method="POST"><div class="form layout-expanded">
{{- form.csrf_token -}}
{%- call render_errors(form) %}{% endcall -%}
<div class="f-ebox">
{{ form.localpart.label }}
<div class="lwrap">{{ form.localpart(class="localpart-magic") }}<span class="localpart-fixed">@{{ config["SNIKKET_DOMAIN"] }}</div>
<p class="field-desc weak">{% trans %}Choose a username, this will become the first part of your new chat address.{% endtrans %}</p>
</div>
<div class="f-ebox">
{{ form.password.label }}
{{ form.password }}
<p class="field-desc weak">{% trans %}Enter a secure password that you do not use anywhere else.{% endtrans %}</p>
</div>
<div class="f-ebox">
{{ form.password_confirm.label }}
{{ form.password_confirm }}
</div>
<div class="f-bbox">
{%- call form_button("done", form.action_register, class="primary") -%}{%- endcall -%}
</div>
</div></form>
</div>
{% endblock %}

View File

@@ -0,0 +1,20 @@
{% extends "invite.html" %}
{% set body_id = "invite" %}
{% from "library.j2" import form_button, clipboard_button %}
{% block head_lead %}
<title>{% trans site_name=config["SITE_NAME"] %}Successfully registered on {{ site_name }} | Snikket{% endtrans %}</title>
{%- include "copy-snippet.html" -%}
{% endblock %}
{% block content %}
<div class="elevated box el-3 form layout-expanded">
<h1>{% trans site_name=config["SITE_NAME"] %}Successfully registered on {{ site_name }}{% endtrans %}</h1>
<div class="powered-by">{% trans logo_url=url_for("static", filename="img/snikket-logo.svg") %}Powered by <img src="{{ logo_url }}" alt="Snikket">{% endtrans %}</div>
<p>{% trans site_name=config["SITE_NAME"], jid=jid %}Congratulations! You successfully registered on {{ site_name }} as {{ jid }}.{% endtrans %}</p>
<input type="text" readonly="readonly" value="{{ jid }}">
{%- call clipboard_button(jid, show_label=True) -%}
{% trans %}Copy address{% endtrans %}
{%- endcall -%}
<p>{% trans %}You can not set up your legacy XMPP client with the above address and the password you chose during registration.{% endtrans %}</p>
<p>{% trans %}You can now safely close this page.{% endtrans %}</p>
</div>
{% endblock %}

View File

@@ -0,0 +1,86 @@
{% extends "invite.html" %}
{% set onload = "onload();" %}
{% set body_id = "invite" %}
{% from "library.j2" import action_button %}
{% block head_lead %}
<title>{% trans site_name=config["SITE_NAME"] %}Invite to {{ site_name }} | Snikket{% endtrans %}</title>
<script async type="text/javascript" src="{{ url_for("static", filename="js/invite-magic.js") }}"></script>
<script async type="text/javascript" src="{{ url_for("static", filename="js/qrcode.min.js") }}"></script>
{% endblock %}
{% block content %}
<div class="elevated box el-3">
<h1>{% trans site_name=config["SITE_NAME"] %}Invite to {{ site_name }}{% endtrans %}</h1>
<div class="powered-by">{% trans logo_url=url_for("static", filename="img/snikket-logo.svg") %}Powered by <img src="{{ logo_url }}" alt="Snikket">{% endtrans %}</div>
{%- if invite.inviter -%}
<p>{% trans site_name=config["SITE_NAME"], inviter_name=invite.inviter %}You have been invited to chat with {{ inviter_name }} using Snikket, a secure, privacy-friendly chat app on {{ site_name }}.{% endtrans %}</p>
{%- else -%}
<p>{% trans site_name=config["SITE_NAME"] %}You have been invited to chat on {{ site_name }} using Snikket, a secure, privacy-friendly chat app.{% endtrans %}</p>
{%- endif -%}
<h2>{% trans %}Get started{% endtrans %}</h2>
<p>{% trans %}Install the Snikket App on your Android or iOS device.{% endtrans %}</p>
<div class="install-buttons">
<ul>
<li><a href="{{ play_store_url }}"><img alt='{% trans %}Get it on Google Play{% endtrans %}' src='https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png' class="play"/></a></li>
<li><a href="{{ apple_store_url }}"><img alt='{% trans %}Download on the App Store{% endtrans %}' src="{{ apple_store_badge() }}" class="apple"></a></li>
</ul>
{%- call standard_button("qrcode", "#qr-modal", class="primary", onclick="open_modal(this); return false;") -%}
{% trans %}Not on mobile?{% endtrans %}
{%- endcall -%}
</div>
<p>{% trans %}After installation the app should automatically open and prompt you to create an account. If not, simply click the button below.{% endtrans %}</p>
<h3>{% trans %}App already installed?{% endtrans %}</h3>
{%- call standard_button("exit_to_app", invite.xmpp_uri, class="secondary") -%}
{% trans %}Open the app{% endtrans %}
{%- endcall -%}
<p class="weak">{% trans %}This button works only if you have the app installed already!{% endtrans %}</p>
<h2>{% trans %}Alternatives{% endtrans %}</h2>
<p>{% trans register_url=url_for(".register", id_=invite_id) %}You can connect to Snikket using any XMPP-compatible software. If the button above does not work with your app, you may need to <a href="{{ register_url }}">register an account manually</a>.{% endtrans %}</p>
</div>
<div id="qr-modal" class="modal" tabindex="-1" role="dialog" aria-hidden="true" style="display: none;" onclick="close_modal(this); return false;">
<div role="document" class="elevated box el-2" onclick="event.stopPropagation();">
<header class="modal-title">
{#- -#}
<span>{% trans %}Scan invite code{% endtrans %}</span>
{#- -#}
{%- call action_button("close", "#", onclick="close_modal(this.parentNode.parentNode.parentNode); return false;", class="tertiary") -%}
{% trans %}Close{% endtrans %}
{%- endcall -%}
</header>
<p>{% trans %}You can transfer this invite to your mobile device by scanning a code with your camera. You can use either a QR scanner app or the Snikket app itself.{% endtrans %}</p>
<div class="tabbox">
{#- -#}
<nav class="tabs" role="tablist">
{#- -#}
<a href="#qr-info-url" class="active" role="tab" aria-controls="qr-info-url" aria-selected="true" onclick="select_tab(this); return false;">{% trans %}Using a QR code scanner{% endtrans %}</a>
{#- -#}
<a href="#qr-info-uri" role="tab" aria-controls="qr-info-uri" aria-selected="false" onclick="select_tab(this); return false;">{% trans %}Using the Snikket app{% endtrans %}</a>
{#- -#}
</nav>
{#- -#}
<div id="qr-info-url" class="tab-pane active">
<p>{% trans %}Use a <em>QR code</em> scanner on your mobile device to scan the code below:{% endtrans %}</p>
<div id="qr-invite-page" data-qrdata="{{ url_for(".view", id_=invite_id, _external=True) }}" class="qr"></div>
</div>
{#- -#}
<div id="qr-info-uri" class="tab-pane">
<img class="float-right" id="tutorial-scan" aria-hidden="true" alt="" src="{{ url_for("static", filename="img/tutorial-scan.png") }}">
<p>{% trans %}Install the Snikket app on your mobile device, open it, and tap the 'Scan' button at the top.{% endtrans %}</p>
<p>{% trans %}Your camera will turn on. Point it at the square code below until it is within the highlighted square on your screen, and wait until the app recognises it.{% endtrans %}</p>
<div id="qr-uri" data-qrdata="{{ invite.xmpp_uri }}" class="qr"></div>
</div>
{#- -#}
</div>
{#- -#}
{%- call standard_button("close", "#", onclick="close_modal(this.parentNode.parentNode); return false;", class="primary") -%}
{% trans %}Close{% endtrans %}
{%- endcall -%}
</div>
</div>
<script type="text/javascript">
var onload = function() {
apply_qr_code(document.getElementById("qr-invite-page"));
apply_qr_code(document.getElementById("qr-uri"));
};
</script>
{% endblock %}

View File

@@ -24,9 +24,9 @@
{%- if alt %}<span class="a11y-only">{{ alt }}</span>{% endif %}<svg class="icon icon-{{ name }}" aria-hidden="true"><use xlink:href="{{ url_for('static', filename='img/icons.svg' )}}#icon-{{ name }}"></use></svg>
{%- endmacro %}
{% macro standard_button(icon_name, href, caller=None, class=None) -%}
{% macro standard_button(icon_name, href, caller=None, class=None, onclick=None) -%}
{%- set label = caller() -%}
<a href="{{ href }}" class="button {% if class %}{{ class }}{% endif %}" aria-label="{{ a11y }}" title="{{ a11y }}">{% call icon(icon_name) %}{% endcall %}<span>{{ label }}</span></a>
<a href="{{ href }}" class="button {% if class %}{{ class }}{% endif %}" aria-label="{{ a11y }}" title="{{ a11y }}"{% if onclick %} onclick="{{ onclick }}"{% endif %}>{% call icon(icon_name) %}{% endcall %}<span>{{ label }}</span></a>
{%- endmacro %}
{% macro form_button(icon_name, button_obj, caller=None, class=None) -%}
@@ -52,9 +52,9 @@
</button>
{%- endmacro %}
{% macro action_button(icon_name, href, caller=None, class=None) -%}
{% macro action_button(icon_name, href, caller=None, class=None, onclick=None) -%}
{%- set a11y = caller() -%}
<a href="{{ href }}" class="button {% if class %}{{ class }}{% endif %}" aria-label="{{ a11y }}" title="{{ a11y }}">{% call icon(icon_name) %}{% endcall %}</a>
<a href="{{ href }}" class="button {% if class %}{{ class }}{% endif %}" aria-label="{{ a11y }}" title="{{ a11y }}"{% if onclick %} onclick="{{ onclick }}"{% endif %}>{% call icon(icon_name) %}{% endcall %}</a>
{%- endmacro %}
{% macro clipboard_button(data, show_label=False, caller=None, class=None) -%}

View File

@@ -8,11 +8,5 @@
{% block topbar_right %}{% endblock %}
</div>
<div id="mwrap"><main>{% block content %}{% endblock %}</main></div>
<footer>
<ul>
{#- -#}
<li>{% trans about_url=url_for('main.about') %}A <a href="{{ about_url }}">Snikket</a> service{% endtrans %}</li>
{#- -#}
</ul>
</footer>
{%- include "_footer.html" -%}
{% endblock %}

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-01-24 11:05+0100\n"
"POT-Creation-Date: 2021-01-25 17:00+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -93,14 +93,42 @@ msgstr ""
msgid "Main"
msgstr ""
#: snikket_web/main.py:36
msgid "Address"
#: snikket_web/invite.py:86
msgid "Username"
msgstr ""
#: snikket_web/main.py:41
#: snikket_web/invite.py:90 snikket_web/main.py:41
msgid "Password"
msgstr ""
#: snikket_web/invite.py:94
msgid "Confirm password"
msgstr ""
#: snikket_web/invite.py:98
msgid "The passwords must match"
msgstr ""
#: snikket_web/invite.py:103
msgid "Create account"
msgstr ""
#: snikket_web/invite.py:123
msgid "That user name is already taken"
msgstr ""
#: snikket_web/invite.py:127
msgid "Registration was declined for unknown reasons"
msgstr ""
#: snikket_web/invite.py:131
msgid "The user name was not valid"
msgstr ""
#: snikket_web/main.py:36
msgid "Address"
msgstr ""
#: snikket_web/main.py:46
msgid "Sign in"
msgstr ""
@@ -159,6 +187,11 @@ msgstr ""
msgid "Incorrect password"
msgstr ""
#: snikket_web/templates/_footer.html:4 snikket_web/templates/login.html:36
#, python-format
msgid "A <a href=\"%(about_url)s\">Snikket</a> service"
msgstr ""
#: snikket_web/templates/about.html:9
msgid "About Snikket"
msgstr ""
@@ -589,6 +622,219 @@ msgstr ""
msgid "The web portal encountered an internal error."
msgstr ""
#: snikket_web/templates/invite_invalid.html:4
#: snikket_web/templates/invite_view.html:11
#, python-format
msgid "Invite to %(site_name)s"
msgstr ""
#: snikket_web/templates/invite_invalid.html:5
#: snikket_web/templates/invite_register.html:9
#: snikket_web/templates/invite_success.html:10
#: snikket_web/templates/invite_view.html:12
#, python-format
msgid "Powered by <img src=\"%(logo_url)s\" alt=\"Snikket\">"
msgstr ""
#: snikket_web/templates/invite_invalid.html:7
msgid "Invite expired"
msgstr ""
#: snikket_web/templates/invite_invalid.html:8
msgid "Sorry, it looks like this invite code has expired!"
msgstr ""
#: snikket_web/templates/invite_register.html:4
#, python-format
msgid "Register on %(site_name)s | Snikket"
msgstr ""
#: snikket_web/templates/invite_register.html:8
#, python-format
msgid "Register on %(site_name)s"
msgstr ""
#: snikket_web/templates/invite_register.html:10
#, python-format
msgid "%(site_name)s is using Snikket - a secure, privacy-friendly chat app."
msgstr ""
#: snikket_web/templates/invite_register.html:11
msgid "Create an account"
msgstr ""
#: snikket_web/templates/invite_register.html:12
msgid ""
"Creating an account will allow to communicate with other people using the"
" Snikket app or compatible software. If you already have the app "
"installed, we recommend that you continue the account creation process "
"inside the app by clicking on the button below:"
msgstr ""
#: snikket_web/templates/invite_register.html:13
#: snikket_web/templates/invite_view.html:30
msgid "App already installed?"
msgstr ""
#: snikket_web/templates/invite_register.html:15
#: snikket_web/templates/invite_view.html:32
msgid "Open the app"
msgstr ""
#: snikket_web/templates/invite_register.html:17
#: snikket_web/templates/invite_view.html:34
msgid "This button works only if you have the app installed already!"
msgstr ""
#: snikket_web/templates/invite_register.html:18
msgid "Create an account online"
msgstr ""
#: snikket_web/templates/invite_register.html:19
msgid ""
"If you plan to use a legacy XMPP client, you can register an account "
"online and enter your credentials into any XMPP-compatible software."
msgstr ""
#: snikket_web/templates/invite_register.html:26
msgid ""
"Choose a username, this will become the first part of your new chat "
"address."
msgstr ""
#: snikket_web/templates/invite_register.html:31
msgid "Enter a secure password that you do not use anywhere else."
msgstr ""
#: snikket_web/templates/invite_success.html:4
#, python-format
msgid "Successfully registered on %(site_name)s | Snikket"
msgstr ""
#: snikket_web/templates/invite_success.html:9
#, python-format
msgid "Successfully registered on %(site_name)s"
msgstr ""
#: snikket_web/templates/invite_success.html:11
#, python-format
msgid "Congratulations! You successfully registered on %(site_name)s as %(jid)s."
msgstr ""
#: snikket_web/templates/invite_success.html:14
msgid "Copy address"
msgstr ""
#: snikket_web/templates/invite_success.html:16
msgid ""
"You can not set up your legacy XMPP client with the above address and the"
" password you chose during registration."
msgstr ""
#: snikket_web/templates/invite_success.html:17
msgid "You can now safely close this page."
msgstr ""
#: snikket_web/templates/invite_view.html:5
#, python-format
msgid "Invite to %(site_name)s | Snikket"
msgstr ""
#: snikket_web/templates/invite_view.html:14
#, python-format
msgid ""
"You have been invited to chat with %(inviter_name)s using Snikket, a "
"secure, privacy-friendly chat app on %(site_name)s."
msgstr ""
#: snikket_web/templates/invite_view.html:16
#, python-format
msgid ""
"You have been invited to chat on %(site_name)s using Snikket, a secure, "
"privacy-friendly chat app."
msgstr ""
#: snikket_web/templates/invite_view.html:18
msgid "Get started"
msgstr ""
#: snikket_web/templates/invite_view.html:19
msgid "Install the Snikket App on your Android or iOS device."
msgstr ""
#: snikket_web/templates/invite_view.html:22
msgid "Get it on Google Play"
msgstr ""
#: snikket_web/templates/invite_view.html:23
msgid "Download on the App Store"
msgstr ""
#: snikket_web/templates/invite_view.html:26
msgid "Not on mobile?"
msgstr ""
#: snikket_web/templates/invite_view.html:29
msgid ""
"After installation the app should automatically open and prompt you to "
"create an account. If not, simply click the button below."
msgstr ""
#: snikket_web/templates/invite_view.html:36
msgid "Alternatives"
msgstr ""
#: snikket_web/templates/invite_view.html:37
#, python-format
msgid ""
"You can connect to Snikket using any XMPP-compatible software. If the "
"button above does not work with your app, you may need to <a "
"href=\"%(register_url)s\">register an account manually</a>."
msgstr ""
#: snikket_web/templates/invite_view.html:43
msgid "Scan invite code"
msgstr ""
#: snikket_web/templates/invite_view.html:46
#: snikket_web/templates/invite_view.html:75
msgid "Close"
msgstr ""
#: snikket_web/templates/invite_view.html:49
msgid ""
"You can transfer this invite to your mobile device by scanning a code "
"with your camera. You can use either a QR scanner app or the Snikket app "
"itself."
msgstr ""
#: snikket_web/templates/invite_view.html:54
msgid "Using a QR code scanner"
msgstr ""
#: snikket_web/templates/invite_view.html:56
msgid "Using the Snikket app"
msgstr ""
#: snikket_web/templates/invite_view.html:61
msgid ""
"Use a <em>QR code</em> scanner on your mobile device to scan the code "
"below:"
msgstr ""
#: snikket_web/templates/invite_view.html:67
msgid ""
"Install the Snikket app on your mobile device, open it, and tap the "
"'Scan' button at the top."
msgstr ""
#: snikket_web/templates/invite_view.html:68
msgid ""
"Your camera will turn on. Point it at the square code below until it is "
"within the highlighted square on your screen, and wait until the app "
"recognises it."
msgstr ""
#: snikket_web/templates/library.j2:18
msgid "Copy link"
msgstr ""
@@ -609,11 +855,6 @@ msgstr ""
msgid "Login failed"
msgstr ""
#: snikket_web/templates/login.html:36 snikket_web/templates/unauth.html:14
#, python-format
msgid "A <a href=\"%(about_url)s\">Snikket</a> service"
msgstr ""
#: snikket_web/templates/user_home.html:3
msgid "Welcome!"
msgstr ""

View File

@@ -3,6 +3,8 @@ action/bug_report:bug_report
action/done:done
action/logout:logout
action/login:login
action/exit_to_app:exit_to_app
communication/qr_code:qrcode
communication/vpn_key:passwd
content/add_circle_outline:add
content/add_link:create_link
@@ -16,3 +18,4 @@ navigation/cancel:cancel
navigation/more_vert:more
social/groups:groups
social/group_add:create_group
navigation/close:close