diff --git a/app/activitypub.py b/app/activitypub.py index 1a20fc7..8e2a1f1 100644 --- a/app/activitypub.py +++ b/app/activitypub.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from typing import Any import httpx +from markdown import markdown from app import config from app.config import AP_CONTENT_TYPE # noqa: F401 @@ -96,7 +97,16 @@ ME = { }, "url": config.ID + "/", # XXX: the path is important for Mastodon compat "manuallyApprovesFollowers": config.CONFIG.manually_approves_followers, - "attachment": [], + "attachment": [ + { + "name": kv.key, + "type": "PropertyValue", + "value": markdown(kv.value, extensions=["mdx_linkify", "fenced_code"]), + } + for kv in config.CONFIG.metadata + ] + if config.CONFIG.metadata + else [], "icon": { "mediaType": mimetypes.guess_type(config.CONFIG.icon_url)[0], "type": "Image", diff --git a/app/actor.py b/app/actor.py index beac556..3761fc5 100644 --- a/app/actor.py +++ b/app/actor.py @@ -257,6 +257,14 @@ def _actor_hash(actor: Actor) -> bytes: if actor.icon_url: h.update(actor.icon_url.encode()) + if actor.attachments: + for a in actor.attachments: + if a.get("type") != "PropertyValue": + continue + + h.update(a["name"].encode()) + h.update(a["value"].encode()) + h.update(actor.public_key_id.encode()) h.update(actor.public_key_as_pem.encode()) diff --git a/app/config.py b/app/config.py index e4708e5..f828f6d 100644 --- a/app/config.py +++ b/app/config.py @@ -36,6 +36,11 @@ class _PrivacyReplace(pydantic.BaseModel): replace_by: str +class _ProfileMetadata(pydantic.BaseModel): + key: str + value: str + + class Config(pydantic.BaseModel): domain: str username: str @@ -49,6 +54,7 @@ class Config(pydantic.BaseModel): trusted_hosts: list[str] = ["127.0.0.1"] manually_approves_followers: bool = False privacy_replace: list[_PrivacyReplace] | None = None + metadata: list[_ProfileMetadata] | None = None code_highlighting_theme = "friendly_grayscale" # Config items to make tests easier diff --git a/app/scss/main.scss b/app/scss/main.scss index a4de209..8265e3b 100644 --- a/app/scss/main.scss +++ b/app/scss/main.scss @@ -51,6 +51,26 @@ a { text-decoration: none; } +dl { + display: flex; + dt { + width: 200px; + flex: 0 0 auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + dd { + flex: 1 1 auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + p { + display: inline; + } + } +} + .shared-header { margin-left: 20px; margin-top: 30px; diff --git a/app/templates/header.html b/app/templates/header.html index 93a33ba..db236e5 100644 --- a/app/templates/header.html +++ b/app/templates/header.html @@ -11,6 +11,17 @@ {{ local_actor.summary | safe }} +
+ {% for prop in local_actor.attachments %} +
+ {% if prop.type == "PropertyValue" %} +
{{ prop.name }}
+
{{ prop.value | clean_html(local_actor) | safe }}
+ {% endif %} +
+ {% endfor %} +
+ {%- macro header_link(url, text) -%} diff --git a/app/templates/utils.html b/app/templates/utils.html index a2040a5..f977b8b 100644 --- a/app/templates/utils.html +++ b/app/templates/utils.html @@ -289,14 +289,14 @@ {% if actor.attachments %}
-
- {% for prop in actor.attachments %} + {% for prop in actor.attachments %} +
{% if prop.type == "PropertyValue" %} -
{{ prop.name }}
-
{{ prop.value | clean_html(actor) | safe }}
+
{{ prop.name }}
+
{{ prop.value | clean_html(actor) | safe }}
{% endif %} - {% endfor %} -
+
+ {% endfor %}
{% endif %} {% endif %}