From edae9a6b6254fa685c26128a8d444783a0b74c74 Mon Sep 17 00:00:00 2001
From: Thomas Sileo
Date: Thu, 21 Jul 2022 22:43:06 +0200
Subject: [PATCH] Add alt text support for attachments
---
README.md | 2 +-
.../c9f204f5611d_alt_text_for_attachments.py | 32 +++++++++++++++++++
app/admin.py | 4 ++-
app/boxes.py | 13 +++++---
app/main.py | 1 -
app/models.py | 3 +-
app/static/new.js | 20 ++++++++++++
app/templates/admin_new.html | 3 +-
app/uploads.py | 8 +++--
9 files changed, 74 insertions(+), 12 deletions(-)
create mode 100644 alembic/versions/c9f204f5611d_alt_text_for_attachments.py
diff --git a/README.md b/README.md
index f4bdad8..2ff82cb 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ It is still in early development, this README will be updated when I get to depl
- Strict access control for your outbox enforced via HTTP signature
- **No** Javascript
- The UI is pure HTML/CSS
- - Except a tiny bit of hand-written JS in the note composer to insert emoji
+ - Except tiny bits of hand-written JS in the note composer to insert emoji and add alt text to images
- IndieWeb citizen
- [IndieAuth](https://www.w3.org/TR/indieauth/) support (OAuth2 extension)
- [Microformats](http://microformats.org/wiki/Main_Page) everywhere
diff --git a/alembic/versions/c9f204f5611d_alt_text_for_attachments.py b/alembic/versions/c9f204f5611d_alt_text_for_attachments.py
new file mode 100644
index 0000000..5da9769
--- /dev/null
+++ b/alembic/versions/c9f204f5611d_alt_text_for_attachments.py
@@ -0,0 +1,32 @@
+"""Alt text for attachments
+
+Revision ID: c9f204f5611d
+Revises: 8ae9fc9ac88c
+Create Date: 2022-07-21 22:33:41.569754
+
+"""
+import sqlalchemy as sa
+
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'c9f204f5611d'
+down_revision = '8ae9fc9ac88c'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('outbox_object_attachment', schema=None) as batch_op:
+ batch_op.add_column(sa.Column('alt', sa.String(), nullable=True))
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('outbox_object_attachment', schema=None) as batch_op:
+ batch_op.drop_column('alt')
+
+ # ### end Alembic commands ###
diff --git a/app/admin.py b/app/admin.py
index 57aaa38..3ef1671 100644
--- a/app/admin.py
+++ b/app/admin.py
@@ -662,10 +662,12 @@ async def admin_actions_new(
) -> RedirectResponse:
# XXX: for some reason, no files restuls in an empty single file
uploads = []
+ raw_form_data = await request.form()
if len(files) >= 1 and files[0].filename:
for f in files:
upload = await save_upload(db_session, f)
- uploads.append((upload, f.filename))
+ uploads.append((upload, f.filename, raw_form_data.get("alt_" + f.filename)))
+
public_id = await boxes.send_create(
db_session,
source=content,
diff --git a/app/boxes.py b/app/boxes.py
index ad8b7f6..6bcb3ed 100644
--- a/app/boxes.py
+++ b/app/boxes.py
@@ -286,7 +286,7 @@ async def send_undo(db_session: AsyncSession, ap_object_id: str) -> None:
async def send_create(
db_session: AsyncSession,
source: str,
- uploads: list[tuple[models.Upload, str]],
+ uploads: list[tuple[models.Upload, str, str | None]],
in_reply_to: str | None,
visibility: ap.VisibilityEnum,
content_warning: str | None = None,
@@ -315,8 +315,8 @@ async def send_create(
.values(replies_count=models.OutboxObject.replies_count + 1)
)
- for (upload, filename) in uploads:
- attachments.append(upload_to_attachment(upload, filename))
+ for (upload, filename, alt_text) in uploads:
+ attachments.append(upload_to_attachment(upload, filename, alt_text))
to = []
cc = []
@@ -366,9 +366,12 @@ async def send_create(
)
db_session.add(tagged_object)
- for (upload, filename) in uploads:
+ for (upload, filename, alt) in uploads:
outbox_object_attachment = models.OutboxObjectAttachment(
- filename=filename, outbox_object_id=outbox_object.id, upload_id=upload.id
+ filename=filename,
+ alt=alt,
+ outbox_object_id=outbox_object.id,
+ upload_id=upload.id,
)
db_session.add(outbox_object_attachment)
diff --git a/app/main.py b/app/main.py
index 4b7defe..ac2443f 100644
--- a/app/main.py
+++ b/app/main.py
@@ -77,7 +77,6 @@ _RESIZED_CACHE: MutableMapping[tuple[str, int], tuple[bytes, str, Any]] = LFUCac
#
# Next:
# - show pending follow request (and prevent double follow?)
-# - a way to add alt text on image (maybe via special markup in content?)
# - UI support for updating posts
# - Support for processing update
# - Article support
diff --git a/app/models.py b/app/models.py
index 80ec2d6..5bda03f 100644
--- a/app/models.py
+++ b/app/models.py
@@ -234,7 +234,7 @@ class OutboxObject(Base, BaseObject):
{
"type": "Document",
"mediaType": attachment.upload.content_type,
- "name": attachment.filename,
+ "name": attachment.alt or attachment.filename,
"url": url,
"proxiedUrl": url,
"resizedUrl": BASE_URL
@@ -403,6 +403,7 @@ class OutboxObjectAttachment(Base):
id = Column(Integer, primary_key=True, index=True)
created_at = Column(DateTime(timezone=True), nullable=False, default=now)
filename = Column(String, nullable=False)
+ alt = Column(String, nullable=True)
outbox_object_id = Column(Integer, ForeignKey("outbox.id"), nullable=False)
diff --git a/app/static/new.js b/app/static/new.js
index e46e9e7..87b02bb 100644
--- a/app/static/new.js
+++ b/app/static/new.js
@@ -30,3 +30,23 @@ var items = document.getElementsByClassName("ji")
for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', ji);
}
+
+// Add new input text dynamically to allow setting an alt text on attachments
+var files = document.getElementById("files");
+var alts = document.getElementById("alts");
+files.addEventListener("change", function(e) {
+ // Reset the div content
+ alts.innerHTML = "";
+
+ // Add an input for each files
+ for (var i = 0; i < e.target.files.length; i++) {
+ var p = document.createElement("p");
+ var altInput = document.createElement("input");
+ altInput.setAttribute("type", "text");
+ altInput.setAttribute("name", "alt_" + e.target.files[i].name);
+ altInput.setAttribute("placeholder", "Alt text for " + e.target.files[i].name);
+ altInput.setAttribute("style", "width:95%;")
+ p.appendChild(altInput);
+ alts.appendChild(p);
+ }
+});
diff --git a/app/templates/admin_new.html b/app/templates/admin_new.html
index 7788c00..4528c05 100644
--- a/app/templates/admin_new.html
+++ b/app/templates/admin_new.html
@@ -39,8 +39,9 @@
-
+
+
diff --git a/app/uploads.py b/app/uploads.py
index 07a9135..9e4cc23 100644
--- a/app/uploads.py
+++ b/app/uploads.py
@@ -96,7 +96,11 @@ async def save_upload(db_session: AsyncSession, f: UploadFile) -> models.Upload:
return new_upload
-def upload_to_attachment(upload: models.Upload, filename: str) -> ap.RawObject:
+def upload_to_attachment(
+ upload: models.Upload,
+ filename: str,
+ alt_text: str | None,
+) -> ap.RawObject:
extra_attachment_fields = {}
if upload.blurhash:
extra_attachment_fields.update(
@@ -109,7 +113,7 @@ def upload_to_attachment(upload: models.Upload, filename: str) -> ap.RawObject:
return {
"type": "Document",
"mediaType": upload.content_type,
- "name": filename,
+ "name": alt_text or filename,
"url": BASE_URL + f"/attachments/{upload.content_hash}/{filename}",
**extra_attachment_fields,
}