Add alt text support for attachments

This commit is contained in:
Thomas Sileo 2022-07-21 22:43:06 +02:00
parent a95dee9ef0
commit edae9a6b62
9 changed files with 74 additions and 12 deletions

View file

@ -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 - Strict access control for your outbox enforced via HTTP signature
- **No** Javascript - **No** Javascript
- The UI is pure HTML/CSS - 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 - IndieWeb citizen
- [IndieAuth](https://www.w3.org/TR/indieauth/) support (OAuth2 extension) - [IndieAuth](https://www.w3.org/TR/indieauth/) support (OAuth2 extension)
- [Microformats](http://microformats.org/wiki/Main_Page) everywhere - [Microformats](http://microformats.org/wiki/Main_Page) everywhere

View file

@ -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 ###

View file

@ -662,10 +662,12 @@ async def admin_actions_new(
) -> RedirectResponse: ) -> RedirectResponse:
# XXX: for some reason, no files restuls in an empty single file # XXX: for some reason, no files restuls in an empty single file
uploads = [] uploads = []
raw_form_data = await request.form()
if len(files) >= 1 and files[0].filename: if len(files) >= 1 and files[0].filename:
for f in files: for f in files:
upload = await save_upload(db_session, f) 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( public_id = await boxes.send_create(
db_session, db_session,
source=content, source=content,

View file

@ -286,7 +286,7 @@ async def send_undo(db_session: AsyncSession, ap_object_id: str) -> None:
async def send_create( async def send_create(
db_session: AsyncSession, db_session: AsyncSession,
source: str, source: str,
uploads: list[tuple[models.Upload, str]], uploads: list[tuple[models.Upload, str, str | None]],
in_reply_to: str | None, in_reply_to: str | None,
visibility: ap.VisibilityEnum, visibility: ap.VisibilityEnum,
content_warning: str | None = None, content_warning: str | None = None,
@ -315,8 +315,8 @@ async def send_create(
.values(replies_count=models.OutboxObject.replies_count + 1) .values(replies_count=models.OutboxObject.replies_count + 1)
) )
for (upload, filename) in uploads: for (upload, filename, alt_text) in uploads:
attachments.append(upload_to_attachment(upload, filename)) attachments.append(upload_to_attachment(upload, filename, alt_text))
to = [] to = []
cc = [] cc = []
@ -366,9 +366,12 @@ async def send_create(
) )
db_session.add(tagged_object) db_session.add(tagged_object)
for (upload, filename) in uploads: for (upload, filename, alt) in uploads:
outbox_object_attachment = models.OutboxObjectAttachment( 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) db_session.add(outbox_object_attachment)

View file

@ -77,7 +77,6 @@ _RESIZED_CACHE: MutableMapping[tuple[str, int], tuple[bytes, str, Any]] = LFUCac
# #
# Next: # Next:
# - show pending follow request (and prevent double follow?) # - 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 # - UI support for updating posts
# - Support for processing update # - Support for processing update
# - Article support # - Article support

View file

@ -234,7 +234,7 @@ class OutboxObject(Base, BaseObject):
{ {
"type": "Document", "type": "Document",
"mediaType": attachment.upload.content_type, "mediaType": attachment.upload.content_type,
"name": attachment.filename, "name": attachment.alt or attachment.filename,
"url": url, "url": url,
"proxiedUrl": url, "proxiedUrl": url,
"resizedUrl": BASE_URL "resizedUrl": BASE_URL
@ -403,6 +403,7 @@ class OutboxObjectAttachment(Base):
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
created_at = Column(DateTime(timezone=True), nullable=False, default=now) created_at = Column(DateTime(timezone=True), nullable=False, default=now)
filename = Column(String, nullable=False) filename = Column(String, nullable=False)
alt = Column(String, nullable=True)
outbox_object_id = Column(Integer, ForeignKey("outbox.id"), nullable=False) outbox_object_id = Column(Integer, ForeignKey("outbox.id"), nullable=False)

View file

@ -30,3 +30,23 @@ var items = document.getElementsByClassName("ji")
for (var i = 0; i < items.length; i++) { for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', ji); 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);
}
});

View file

@ -39,8 +39,9 @@
</p> </p>
<input type="hidden" name="in_reply_to" value="{{ request.query_params.in_reply_to }}"> <input type="hidden" name="in_reply_to" value="{{ request.query_params.in_reply_to }}">
<p> <p>
<input name="files" type="file" multiple> <input id="files" name="files" type="file" multiple style="width:95%;">
</p> </p>
<div id="alts"></div>
<p> <p>
<input type="submit" value="Publish"> <input type="submit" value="Publish">
</p> </p>

View file

@ -96,7 +96,11 @@ async def save_upload(db_session: AsyncSession, f: UploadFile) -> models.Upload:
return new_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 = {} extra_attachment_fields = {}
if upload.blurhash: if upload.blurhash:
extra_attachment_fields.update( extra_attachment_fields.update(
@ -109,7 +113,7 @@ def upload_to_attachment(upload: models.Upload, filename: str) -> ap.RawObject:
return { return {
"type": "Document", "type": "Document",
"mediaType": upload.content_type, "mediaType": upload.content_type,
"name": filename, "name": alt_text or filename,
"url": BASE_URL + f"/attachments/{upload.content_hash}/{filename}", "url": BASE_URL + f"/attachments/{upload.content_hash}/{filename}",
**extra_attachment_fields, **extra_attachment_fields,
} }