diff --git a/announcements/src/org/labkey/announcements/announcementThread.jsp b/announcements/src/org/labkey/announcements/announcementThread.jsp
index b636af6df12..1b8711bc415 100644
--- a/announcements/src/org/labkey/announcements/announcementThread.jsp
+++ b/announcements/src/org/labkey/announcements/announcementThread.jsp
@@ -127,7 +127,7 @@ if (!announcementModel.getAttachments().isEmpty())
{
ActionURL downloadURL = AnnouncementsController.getDownloadURL(announcementModel, d.getName());
%>
-
<%=h(d.getName())%> <%
+ <%=d.renderDownloadLink(downloadURL)%> <%
} %>
<%
@@ -210,7 +210,7 @@ if (!announcementModel.getResponses().isEmpty())
{
ActionURL downloadURL = AnnouncementsController.getDownloadURL(r, rd.getName());
%>
-
<%=h(rd.getName())%> <%
+ <%=rd.renderDownloadLink(downloadURL)%> <%
}
%>
diff --git a/announcements/src/org/labkey/announcements/announcementWebPartSimple.jsp b/announcements/src/org/labkey/announcements/announcementWebPartSimple.jsp
index 713869f8ee8..dac75ebc99c 100644
--- a/announcements/src/org/labkey/announcements/announcementWebPartSimple.jsp
+++ b/announcements/src/org/labkey/announcements/announcementWebPartSimple.jsp
@@ -195,7 +195,7 @@ for (AnnouncementModel a : bean.announcementModels)
for (Attachment d : a.getAttachments())
{
ActionURL downloadURL = AnnouncementsController.getDownloadURL(a, d.getName());
- %>
<%=h(d.getName())%> <%
+ %><%=d.renderDownloadLink(downloadURL)%> <%
}
%><%
}
diff --git a/announcements/src/org/labkey/announcements/announcementWebPartWithExpandos.jsp b/announcements/src/org/labkey/announcements/announcementWebPartWithExpandos.jsp
index 4322dc4b7ed..8f061e75af7 100644
--- a/announcements/src/org/labkey/announcements/announcementWebPartWithExpandos.jsp
+++ b/announcements/src/org/labkey/announcements/announcementWebPartWithExpandos.jsp
@@ -217,7 +217,7 @@ for (AnnouncementModel a : bean.announcementModels)
for (Attachment d : a.getAttachments())
{
ActionURL downloadURL = AnnouncementsController.getDownloadURL(a, d.getName());
- %>
<%=h(d.getName())%> <%
+ %><%=d.renderDownloadLink(downloadURL)%> <%
}
%><%
}
diff --git a/announcements/src/org/labkey/announcements/update.jsp b/announcements/src/org/labkey/announcements/update.jsp
index 1797588ed54..c5e54ef947b 100644
--- a/announcements/src/org/labkey/announcements/update.jsp
+++ b/announcements/src/org/labkey/announcements/update.jsp
@@ -140,7 +140,6 @@ if (settings.hasExpires())
<%
int x = -1;
- String id;
for (Attachment att : ann.getAttachments())
{
x++;
diff --git a/api/src/org/labkey/api/attachments/Attachment.java b/api/src/org/labkey/api/attachments/Attachment.java
index 904972dae6e..b946c8012d4 100644
--- a/api/src/org/labkey/api/attachments/Attachment.java
+++ b/api/src/org/labkey/api/attachments/Attachment.java
@@ -21,9 +21,13 @@
import org.labkey.api.security.User;
import org.labkey.api.security.UserManager;
import org.labkey.api.services.ServiceRegistry;
+import org.labkey.api.util.DOM;
+import org.labkey.api.util.HtmlString;
import org.labkey.api.util.MemTracker;
import org.labkey.api.util.MimeMap;
+import org.labkey.api.util.PageFlowUtil;
import org.labkey.api.util.Path;
+import org.labkey.api.view.ActionURL;
import org.labkey.api.view.ViewServlet;
import org.labkey.api.webdav.WebdavResolver;
@@ -350,4 +354,29 @@ public void setDocumentSize(int documentSize)
{
_documentSize = documentSize;
}
+
+ /**
+ * Returns an HtmlString rendering a download link: an anchor containing a file type icon and the filename.
+ * The icon is marked aria-hidden since it is decorative; the link text serves as the accessible name.
+ */
+ public HtmlString renderDownloadLink(ActionURL downloadURL)
+ {
+ return renderDownloadLink(downloadURL, getName());
+ }
+
+ /**
+ * Returns an HtmlString rendering a download link: an anchor containing a file type icon and custom link text.
+ * Use this overload when the visible link label differs from the filename (e.g. "Study Protocol Document").
+ * The icon is marked aria-hidden since it is decorative; linkText serves as the accessible name.
+ */
+ public HtmlString renderDownloadLink(ActionURL downloadURL, String linkText)
+ {
+ return DOM.createHtmlFragment(
+ DOM.A(DOM.at(DOM.Attribute.href, downloadURL.toString()),
+ DOM.IMG(DOM.at(DOM.Attribute.alt, "").at(DOM.Attribute.src, PageFlowUtil.staticResourceUrl(getFileIcon()))),
+ HtmlString.NBSP,
+ linkText
+ )
+ );
+ }
}
diff --git a/api/src/org/labkey/api/util/DOM.java b/api/src/org/labkey/api/util/DOM.java
index 1feb02da7b1..140e91deba9 100644
--- a/api/src/org/labkey/api/util/DOM.java
+++ b/api/src/org/labkey/api/util/DOM.java
@@ -362,6 +362,54 @@ public enum Attribute
action,
align,
alt,
+ aria_activedescendant,
+ aria_atomic,
+ aria_autocomplete,
+ aria_busy,
+ aria_checked,
+ aria_colcount,
+ aria_colindex,
+ aria_colspan,
+ aria_controls,
+ aria_current,
+ aria_describedby,
+ aria_details,
+ aria_disabled,
+ aria_dropeffect,
+ aria_errormessage,
+ aria_expanded,
+ aria_flowto,
+ aria_grabbed,
+ aria_haspopup,
+ aria_hidden,
+ aria_invalid,
+ aria_keyshortcuts,
+ aria_label,
+ aria_labelledby,
+ aria_level,
+ aria_live,
+ aria_modal,
+ aria_multiline,
+ aria_multiselectable,
+ aria_orientation,
+ aria_owns,
+ aria_placeholder,
+ aria_posinset,
+ aria_pressed,
+ aria_readonly,
+ aria_relevant,
+ aria_required,
+ aria_roledescription,
+ aria_rowcount,
+ aria_rowindex,
+ aria_rowspan,
+ aria_selected,
+ aria_setsize,
+ aria_sort,
+ aria_valuemax,
+ aria_valuemin,
+ aria_valuenow,
+ aria_valuetext,
async,
autocomplete,
autofocus,
@@ -570,7 +618,6 @@ public _Attributes data(boolean condition, String datakey, Object value)
}
return this;
}
-
public _Attributes cl(String...names)
{
if (null != names)
@@ -891,7 +938,7 @@ private static Appendable appendAttribute(Appendable html, Attribute key, Object
if (null==value)
return html;
html.append(" ");
- html.append(key.name());
+ html.append(key.name().replace('_', '-'));
html.append("=\"");
// NOTE it is somewhat unusual to pass in a Renderable, but it is possible that we
// want to render HTML into an attribute. We still need to re-encode the value before trying to wrap with "".
diff --git a/api/webapp/clientapi/dom/DataRegion.js b/api/webapp/clientapi/dom/DataRegion.js
index 07ddf75f5cc..a13e1f2c335 100644
--- a/api/webapp/clientapi/dom/DataRegion.js
+++ b/api/webapp/clientapi/dom/DataRegion.js
@@ -1713,8 +1713,8 @@ if (!LABKEY.DataRegions) {
ct.append([
'',
- '',
- '',
+ '',
+ '',
'
'
].join(''));
diff --git a/core/src/org/labkey/core/login/resetPassword.jsp b/core/src/org/labkey/core/login/resetPassword.jsp
index ea9e89d1c86..9a62a04ad5a 100644
--- a/core/src/org/labkey/core/login/resetPassword.jsp
+++ b/core/src/org/labkey/core/login/resetPassword.jsp
@@ -46,7 +46,7 @@
<% } %>