forked from forks/microblog.pub
Add alt text support for attachments
This commit is contained in:
parent
a95dee9ef0
commit
edae9a6b62
9 changed files with 74 additions and 12 deletions
|
@ -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
|
||||||
|
|
32
alembic/versions/c9f204f5611d_alt_text_for_attachments.py
Normal file
32
alembic/versions/c9f204f5611d_alt_text_for_attachments.py
Normal 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 ###
|
|
@ -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,
|
||||||
|
|
13
app/boxes.py
13
app/boxes.py
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue