Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
- Increase maxproc for reinjecting ports from 10 to 100
([#646](https://github.com/chatmail/relay/pull/646))

- Add markdown tabs blocks for rendering multilingual pages.
Add russian language support to `index.md`, `privacy.md`, and `info.md`.
([#658](https://github.com/chatmail/relay/pull/658))

- Allow ports 143 and 993 to be used by `dovecot` process
([#639](https://github.com/chatmail/relay/pull/639))

Expand Down
4 changes: 4 additions & 0 deletions chatmaild/src/chatmaild/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ def __init__(self, inipath, params):
self.password_min_length = int(params["password_min_length"])
self.passthrough_senders = params["passthrough_senders"].split()
self.passthrough_recipients = params["passthrough_recipients"].split()
self.is_development_instance = (
params.get("is_development_instance", "true").lower() == "true"
)
self.languages = (params.get("languages", "EN").split())
self.www_folder = params.get("www_folder", "")
self.filtermail_smtp_port = int(params["filtermail_smtp_port"])
self.filtermail_smtp_port_incoming = int(
Expand Down
6 changes: 6 additions & 0 deletions chatmaild/src/chatmaild/ini/chatmail.ini.f
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@
# Deployment Details
#

# A space-separated list of languages to be displayed on the site.
# Now available languages: EN RU
# You can also use the keyword "ALL"
# NOTE: The order of languages affects their order on the page
languages = EN

# SMTP outgoing filtermail and reinjection
filtermail_smtp_port = 10080
postfix_reinject_port = 10025
Expand Down
1 change: 1 addition & 0 deletions cmdeploy/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies = [
"pytest-xdist",
"execnet",
"imap_tools",
"pymdown-extensions",
]

[project.scripts]
Expand Down
92 changes: 75 additions & 17 deletions cmdeploy/src/cmdeploy/www.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@

from .genqr import gen_qr_png_data

LANGUAGE_NAMES = {
"EN": " 🇬🇧 English",
"RU": " 🇷🇺 Русский",
# "UA": "Українська",
# "FR": "Français",
# "DE": "Deutsch",
}

def snapshot_dir_stats(somedir):
d = {}
Expand All @@ -22,12 +29,59 @@ def snapshot_dir_stats(somedir):
return d


def prepare_template(source):
assert source.exists(), source
render_vars = {}
render_vars["pagename"] = "home" if source.stem == "index" else source.stem
render_vars["markdown_html"] = markdown.markdown(source.read_text())
page_layout = source.with_name("page-layout.html").read_text()
def prepare_template(source, locales_dir, languages=["EN"]):
assert source.exists(), f"Template {source} not found."
assert locales_dir.exists(), f"Locales directory {locales_dir} not found."
base_name = source.stem
render_vars = {
"pagename": "home" if base_name == "index" else base_name
}

selected_langs = (
sorted([d.name.upper() for d in locales_dir.iterdir() if d.is_dir()])
if "ALL" in [l.upper() for l in languages]
else [l.upper() for l in languages]
)

markdown_blocks = []

tabs_enabled = False
if len(selected_langs) > 1:
tabs_enabled = True

for lang_code in selected_langs:
lang_folder = locales_dir / lang_code
lang_file = lang_folder / f"{base_name}.md"
lang_name = LANGUAGE_NAMES.get(lang_code, lang_code)

if lang_file.exists():
content = lang_file.read_text().strip()
else:
print(f"[WARNING]: Missing file {lang_file}. Inserting fallback message.")
content = "Content for this language is not available, please contact your server administrator."

if tabs_enabled:
markdown_blocks.append(f"/// tab | {lang_name}\n{content}\n///")
continue

markdown_blocks.append(content)

if not markdown_blocks:
print("[WARNING] No valid language content found. Skipping file.")
return None, None

original_markdown = source.read_text()
combined_markdown = original_markdown.replace("%content placeholder%", "\n\n".join(markdown_blocks))

render_vars["markdown_html"] = markdown.markdown(
combined_markdown,
extensions=["pymdownx.blocks.tab"]
)

page_layout_path = source.with_name("page-layout.html")
assert page_layout_path.exists(), f"Missing template: {page_layout_path}"
page_layout = page_layout_path.read_text()

return render_vars, page_layout


Expand Down Expand Up @@ -80,25 +134,27 @@ def int_to_english(number):

def _build_webpages(src_dir, build_dir, config):
mail_domain = config.mail_domain
languages = config.languages
assert src_dir.exists(), src_dir
if not build_dir.exists():
build_dir.mkdir()

qr_path = build_dir.joinpath(f"qr-chatmail-invite-{mail_domain}.png")
qr_path.write_bytes(gen_qr_png_data(mail_domain).read())

locales_dir = src_dir / "locales"

for path in src_dir.iterdir():
if path.suffix == ".md":
render_vars, content = prepare_template(path)
render_vars["username_min_length"] = int_to_english(
config.username_min_length
)
render_vars["username_max_length"] = int_to_english(
config.username_max_length
)
render_vars["password_min_length"] = int_to_english(
config.password_min_length
)
render_vars, content = prepare_template(path, locales_dir, languages)

if render_vars is None:
continue

render_vars["username_min_length"] = int_to_english(config.username_min_length)
render_vars["username_max_length"] = int_to_english(config.username_max_length)
render_vars["password_min_length"] = int_to_english(config.password_min_length)

target = build_dir.joinpath(path.stem + ".html")

# recursive jinja2 rendering
Expand All @@ -110,9 +166,11 @@ def _build_webpages(src_dir, build_dir, config):

with target.open("w") as f:
f.write(content)
elif path.name != "page-layout.html":

elif path.name != "page-layout.html" and path.name != "locales":
target = build_dir.joinpath(path.name)
target.write_bytes(path.read_bytes())

return build_dir


Expand Down
25 changes: 2 additions & 23 deletions www/src/index.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,8 @@

<img class="banner" src="collage-top.png"/>

## Dear [Delta Chat](https://get.delta.chat) users and newcomers ...
%content placeholder%

{% if config.mail_domain != "nine.testrun.org" %}
Welcome to instant, interoperable and [privacy-preserving](privacy.html) messaging :)
{% else %}
Welcome to the default onboarding server ({{ config.mail_domain }})
for Delta Chat users. For details how it avoids storing personal information
please see our [privacy policy](privacy.html).
{% endif %}

<a class="cta-button" href="DCACCOUNT:https://{{ config.mail_domain }}/new">Get a {{config.mail_domain}} chat profile</a>

If you are viewing this page on a different device
without a Delta Chat app,
you can also **scan this QR code** with Delta Chat:

<a href="DCACCOUNT:https://{{ config.mail_domain }}/new">
<img width=300 style="float: none;" src="qr-chatmail-invite-{{config.mail_domain}}.png" /></a>

🐣 **Choose** your Avatar and Name

💬 **Start** chatting with any Delta Chat contacts using [QR invite codes](https://delta.chat/en/help#howtoe2ee)

{% if config.mail_domain != "nine.testrun.org" %}
{% if config.is_development_instance == True %}
<div class="experimental">Note: this is only a temporary development chatmail service</div>
{% endif %}
44 changes: 2 additions & 42 deletions www/src/info.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,3 @@
<img class="banner" src="collage-info.png"/>

## More information

{{ config.mail_domain }} provides a low-maintenance, resource efficient and
interoperable e-mail service for everyone. What's behind a `chatmail` is
effectively a normal e-mail address just like any other but optimized
for the usage in chats, especially DeltaChat.


### Rate and storage limits

- Un-encrypted messages are blocked to recipients outside
{{config.mail_domain}} but setting up contact via [QR invite codes](https://delta.chat/en/help#howtoe2ee)
allows your messages to pass freely to any outside recipients.

- You may send up to {{ config.max_user_send_per_minute }} messages per minute.

- You can store up to [{{ config.max_mailbox_size }} messages on the server](https://delta.chat/en/help#what-happens-if-i-turn-on-delete-old-messages-from-server).

- Messages are unconditionally removed latest {{ config.delete_mails_after }} days after arriving on the server.
Earlier, if storage may exceed otherwise.


### <a name="account-deletion"></a> Account deletion

If you remove a {{ config.mail_domain }} profile from within the Delta Chat app,
then the according account on the server, along with all associated data,
is automatically deleted {{ config.delete_inactive_users_after }} days afterwards.

If you use multiple devices
then you need to remove the according chat profile from each device
in order for all account data to be removed on the server side.

If you have any further questions or requests regarding account deletion
please send a message from your account to {{ config.privacy_mail }}.


### Who are the operators? Which software is running?

This chatmail provider is run by a small voluntary group of devs and sysadmins,
who [publically develop chatmail provider setups](https://github.com/deltachat/chatmail).
Chatmail setups aim to be very low-maintenance, resource efficient and
interoperable with any other standards-compliant e-mail service.
%content placeholder%
54 changes: 54 additions & 0 deletions www/src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,57 @@ code {
color: white !important;
font-weight: bold;
}

.tabbed-set {
position: relative;
display: flex;
flex-wrap: wrap;
margin: 1em 0;
border-radius: 0.1rem;
}

.tabbed-set > input {
display: none;
}

.tabbed-set label {
width: auto;
padding: 0.9375em 1.25em 0.78125em;
font-weight: 700;
font-size: 0.84em;
white-space: nowrap;
border-bottom: 0.15rem solid transparent;
border-top-left-radius: 0.1rem;
border-top-right-radius: 0.1rem;
cursor: pointer;
transition: background-color 250ms, color 250ms;
}

.tabbed-set .tabbed-content {
width: 100%;
display: none;
box-shadow: 0 -.05rem #ddd;
}

.tabbed-set input {
position: absolute;
opacity: 0;
}

.tabbed-set input:checked:nth-child(n+1) + label {
color: red;
border-color: red;
}

@media screen {
.tabbed-set input:nth-child(n+1):checked + label + .tabbed-content {
order: 99;
display: block;
}
}

@media print {
.tabbed-content {
display: contents;
}
}
Loading
Loading