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, }