diff --git a/component/api/pom.xml b/component/api/pom.xml
index cc1963c8843..f124e2c3c83 100644
--- a/component/api/pom.xml
+++ b/component/api/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
social-component-api
Meeds:: PLF:: Social API
diff --git a/component/common/pom.xml b/component/common/pom.xml
index d997d1baeb1..3a76ed94f5f 100644
--- a/component/common/pom.xml
+++ b/component/common/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
io.meeds.social
social-component-common
diff --git a/component/core/pom.xml b/component/core/pom.xml
index 916070138fc..8f18ddf45d3 100644
--- a/component/core/pom.xml
+++ b/component/core/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
social-component-core
Meeds:: PLF:: Social Core Component
diff --git a/component/notification/pom.xml b/component/notification/pom.xml
index 2ca9bbaac27..35a6b4f1ea5 100644
--- a/component/notification/pom.xml
+++ b/component/notification/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
social-component-notification
Meeds:: PLF:: Social Notification Component
diff --git a/component/oauth-auth/pom.xml b/component/oauth-auth/pom.xml
index c7bb2a3e80f..6fc5359ee0b 100644
--- a/component/oauth-auth/pom.xml
+++ b/component/oauth-auth/pom.xml
@@ -21,7 +21,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
4.0.0
diff --git a/component/pom.xml b/component/pom.xml
index b5976751ba8..4138bbb02cb 100644
--- a/component/pom.xml
+++ b/component/pom.xml
@@ -22,7 +22,7 @@
social
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
social-component
pom
diff --git a/component/service/pom.xml b/component/service/pom.xml
index c6b52444ca4..0b8aa3c8aec 100644
--- a/component/service/pom.xml
+++ b/component/service/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
social-component-service
Meeds:: PLF:: Social Service Component
diff --git a/component/web/pom.xml b/component/web/pom.xml
index 90866efe91e..4e2dc359a26 100644
--- a/component/web/pom.xml
+++ b/component/web/pom.xml
@@ -22,7 +22,7 @@
social-component
io.meeds.social
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
io.meeds.social
social-component-web
diff --git a/pom.xml b/pom.xml
index ab09c7291e8..557d51871e9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
io.meeds.social
social
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
pom
Meeds:: PLF:: Social
Meeds Social - Enterprise Social Networking
@@ -45,8 +45,8 @@
- 7.3.x-SNAPSHOT
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
meeds-io
diff --git a/webapp/.eslintrc.json b/webapp/.eslintrc.json
index 5be9841ab8d..17af6b1bf0d 100644
--- a/webapp/.eslintrc.json
+++ b/webapp/.eslintrc.json
@@ -14,6 +14,7 @@
"QRCode": true,
"DOMPurify": true,
"ExtendedDomPurify": true,
+ "EmojiAccessibility": true,
"Cropper": true,
"Autolinker": true,
"cCometd": true,
diff --git a/webapp/pom.xml b/webapp/pom.xml
index 4c6ca6afe37..6c440787426 100644
--- a/webapp/pom.xml
+++ b/webapp/pom.xml
@@ -22,7 +22,7 @@
io.meeds.social
social
- 7.3.x-SNAPSHOT
+ 7.3.x-maintenance-SNAPSHOT
social-webapp
war
diff --git a/webapp/src/main/resources/locale/portlet/Portlets_en.properties b/webapp/src/main/resources/locale/portlet/Portlets_en.properties
index 6a052faf2e7..f6e6c4c0930 100644
--- a/webapp/src/main/resources/locale/portlet/Portlets_en.properties
+++ b/webapp/src/main/resources/locale/portlet/Portlets_en.properties
@@ -716,7 +716,8 @@ GroupsManagement.error.invalidField=The "{0}" field must start with a character
#####################################################################################
# Search #
#####################################################################################
-Search.label.inputPlaceHolder=Search for content...
+Search.label.inputPlaceHolder=Start searching content...
+Search.label.ariaLabel=Search for content
Search.label.inProgress=Search in progress
Search.button.tooltip=Search
Search.button.tooltip.shortcut=(Ctrl + Shift + f)
@@ -807,6 +808,7 @@ Favorite.tooltip.ErrorDeletingFavorite=An error occurred while deleting the {0}
Tag.tooltip.startSearch=Start a search based on this tag
Tag.search.button=Tags
Tag.search.placeholder=Start typing to search a Tag
+Tag.search.ariaLabel=Filter by tag name
Tag.last.added=Last tags added
#####################################################################################
diff --git a/webapp/src/main/resources/locale/portlet/social/PeopleListApplication_en.properties b/webapp/src/main/resources/locale/portlet/social/PeopleListApplication_en.properties
index 38bac0c6345..e559cc122d0 100644
--- a/webapp/src/main/resources/locale/portlet/social/PeopleListApplication_en.properties
+++ b/webapp/src/main/resources/locale/portlet/social/PeopleListApplication_en.properties
@@ -33,6 +33,7 @@ peopleList.title.spacesCount=Spaces
peopleList.title.usersToInvite=Invite users
peopleList.label.peopleCount=Showing {0} results
peopleList.label.filterPeople=Filter by name
+peopleList.label.filterPeople.placeholder=Search for a user
peopleList.label.filter=Filter
peopleList.label.filter.all=Everyone
peopleList.label.filter.connections=My Network
diff --git a/webapp/src/main/resources/locale/portlet/social/SpacesListApplication_en.properties b/webapp/src/main/resources/locale/portlet/social/SpacesListApplication_en.properties
index af647a47b6b..f138baf79f1 100644
--- a/webapp/src/main/resources/locale/portlet/social/SpacesListApplication_en.properties
+++ b/webapp/src/main/resources/locale/portlet/social/SpacesListApplication_en.properties
@@ -10,6 +10,7 @@ spacesList.label.members={0} Members
spacesList.label.oneMember=1 Member
spacesList.label.SpaceMembers=Members
spacesList.label.filterSpaces=Filter by name or description
+spacesList.label.filterSpaces.placeholder=Search for a space
spacesList.label.filterSpacesByName=Filter by name
spacesList.label.spacesSize={0} spaces listed
spacesList.label.ok=OK
diff --git a/webapp/src/main/webapp/WEB-INF/gatein-resources.xml b/webapp/src/main/webapp/WEB-INF/gatein-resources.xml
index f56b841c355..2dcebc6febc 100644
--- a/webapp/src/main/webapp/WEB-INF/gatein-resources.xml
+++ b/webapp/src/main/webapp/WEB-INF/gatein-resources.xml
@@ -2829,6 +2829,9 @@
ExtendedDomPurify
+
+ EmojiAccessibility
+
vue
@@ -2849,6 +2852,15 @@
+
+ EmojiAccessibility
+ purifyGRP
+
+
+
purify
purifyGRP
diff --git a/webapp/src/main/webapp/js/EmojiAccessibility.js b/webapp/src/main/webapp/js/EmojiAccessibility.js
new file mode 100644
index 00000000000..aaf9118736c
--- /dev/null
+++ b/webapp/src/main/webapp/js/EmojiAccessibility.js
@@ -0,0 +1,96 @@
+/**
+ * This file is part of the Meeds project (https://meeds.io/).
+ *
+ * Copyright (C) 2020 - 2022 Meeds Association contact@meeds.io
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+(function() {
+ // Accessibility (#4246): expose emojis to screen readers by wrapping
+ // them in a once their names are loaded.
+ let emojiNameByChar = null;
+ let emojiPattern = null;
+
+ function escapeRegExp(value) {
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ }
+
+ function buildEmojiIndex(emojiBank) {
+ const map = new Map();
+ (emojiBank.categories || []).forEach(category => {
+ (category.emojis || []).forEach(item => {
+ if (item.emoji && item.name && !map.has(item.emoji)) {
+ map.set(item.emoji, item.name);
+ }
+ });
+ });
+ emojiNameByChar = map;
+ const chars = Array.from(map.keys()).sort((a, b) => b.length - a.length);
+ emojiPattern = chars.length && new RegExp(chars.map(escapeRegExp).join('|'), 'g');
+ }
+
+ let ready = false;
+ const readyPromise = fetch('/social/json/emojiBank.json?v=1')
+ .then(resp => resp.ok && resp.json())
+ .then(emojiBank => emojiBank && buildEmojiIndex(emojiBank))
+ .catch(() => {
+ // Emoji names not available: emojis will remain unlabeled.
+ })
+ .then(() => {
+ ready = true;
+ });
+
+ function addAccessibleNameToEmojis(html) {
+ if (!emojiPattern) {
+ return html;
+ }
+ const container = document.createElement('div');
+ container.innerHTML = html;
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
+ const textNodes = [];
+ let node;
+ while ((node = walker.nextNode())) {
+ emojiPattern.lastIndex = 0;
+ if (node.parentElement?.getAttribute('role') !== 'img' && emojiPattern.test(node.nodeValue)) {
+ textNodes.push(node);
+ }
+ }
+ textNodes.forEach(textNode => {
+ const fragment = document.createDocumentFragment();
+ let lastIndex = 0;
+ let match;
+ emojiPattern.lastIndex = 0;
+ while ((match = emojiPattern.exec(textNode.nodeValue))) {
+ if (match.index > lastIndex) {
+ fragment.appendChild(document.createTextNode(textNode.nodeValue.slice(lastIndex, match.index)));
+ }
+ const span = document.createElement('span');
+ span.setAttribute('role', 'img');
+ span.setAttribute('aria-label', emojiNameByChar.get(match[0]));
+ span.textContent = match[0];
+ fragment.appendChild(span);
+ lastIndex = match.index + match[0].length;
+ }
+ fragment.appendChild(document.createTextNode(textNode.nodeValue.slice(lastIndex)));
+ textNode.parentNode.replaceChild(fragment, textNode);
+ });
+ return container.innerHTML;
+ }
+
+ window.EmojiAccessibility = {
+ isReady: () => ready,
+ ready: readyPromise,
+ addAccessibleNameToEmojis,
+ };
+})();
diff --git a/webapp/src/main/webapp/js/ExtendedDomPurify.js b/webapp/src/main/webapp/js/ExtendedDomPurify.js
index e2ae04e3bd7..552c5b89ae2 100644
--- a/webapp/src/main/webapp/js/ExtendedDomPurify.js
+++ b/webapp/src/main/webapp/js/ExtendedDomPurify.js
@@ -43,7 +43,7 @@
case 'url' :
const url = match.getUrl();
const tag = match.buildTag();
- tag.setAttr('href', encodeURI(url));
+ tag.setAttr('href', url);
if (match.getUrl().indexOf('/') === 0 || match.getUrl().indexOf(window.location.origin) === 0) {
return true;
} else {
diff --git a/webapp/src/main/webapp/js/sanitize-html-directive.js b/webapp/src/main/webapp/js/sanitize-html-directive.js
index 96da9e153f9..9667763d6fe 100644
--- a/webapp/src/main/webapp/js/sanitize-html-directive.js
+++ b/webapp/src/main/webapp/js/sanitize-html-directive.js
@@ -17,6 +17,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
(function() {
+ function setSanitizedHtml(el, html) {
+ el.innerHTML = html && EmojiAccessibility.addAccessibleNameToEmojis(html) || '';
+ if (html && !EmojiAccessibility.isReady()) {
+ EmojiAccessibility.ready.then(() => {
+ el.innerHTML = EmojiAccessibility.addAccessibleNameToEmojis(html);
+ });
+ }
+ }
window.Vue.directive('sanitized-html', function (el, binding) {
let content = binding.value;
if (content) {
@@ -36,7 +44,7 @@
if (!el.classList.contains('reset-style-box')) {
el.classList.add('reset-style-box');
}
- el.innerHTML = content && ExtendedDomPurify.purify(content) || '';
+ setSanitizedHtml(el, content && ExtendedDomPurify.purify(content));
});
window.Vue.directive('sanitized-html-no-embed', function (el, binding) {
let content = binding.value;
@@ -54,6 +62,6 @@
}
}
- el.innerHTML = content && ExtendedDomPurify.purify(content) || '';
+ setSanitizedHtml(el, content && ExtendedDomPurify.purify(content));
});
})();
diff --git a/webapp/src/main/webapp/vue-apps/activity-stream/components/activity/content/ActivityEmbeddedHTML.vue b/webapp/src/main/webapp/vue-apps/activity-stream/components/activity/content/ActivityEmbeddedHTML.vue
index 65d4d603b83..8a39ba54579 100644
--- a/webapp/src/main/webapp/vue-apps/activity-stream/components/activity/content/ActivityEmbeddedHTML.vue
+++ b/webapp/src/main/webapp/vue-apps/activity-stream/components/activity/content/ActivityEmbeddedHTML.vue
@@ -41,7 +41,13 @@ export default {
data: () => ({
maxWidth: 320,
elementReady: false,
+ emojiBankReady: EmojiAccessibility.isReady(),
}),
+ created() {
+ if (!this.emojiBankReady) {
+ EmojiAccessibility.ready.then(() => this.emojiBankReady = true);
+ }
+ },
computed: {
embeddedHTML() {
const htmlElement = this.activity?.templateParams?.html;
@@ -89,7 +95,8 @@ export default {
methods: {
computeEmbeddedHTML(htmlElement) {
const tempdiv = document.createElement('div');
- tempdiv.innerHTML = ExtendedDomPurify.purify(htmlElement);
+ const purifiedHtml = ExtendedDomPurify.purify(htmlElement);
+ tempdiv.innerHTML = this.emojiBankReady && EmojiAccessibility.addAccessibleNameToEmojis(purifiedHtml) || purifiedHtml;
if (tempdiv.firstElementChild.style.maxWidth) {
tempdiv.firstElementChild.style.maxWidth = `${this.maxWidth}px`;
}
diff --git a/webapp/src/main/webapp/vue-apps/activity-stream/components/activity/content/ActivityLink.vue b/webapp/src/main/webapp/vue-apps/activity-stream/components/activity/content/ActivityLink.vue
index 6154a28ebd0..fa6f8b05106 100644
--- a/webapp/src/main/webapp/vue-apps/activity-stream/components/activity/content/ActivityLink.vue
+++ b/webapp/src/main/webapp/vue-apps/activity-stream/components/activity/content/ActivityLink.vue
@@ -186,7 +186,8 @@ export default {
useEmbeddedLinkView: true,
summaryLinesToDisplay: 2,
isLandscapeThumbnail: false,
- activityViews: null
+ activityViews: null,
+ emojiBankReady: EmojiAccessibility.isReady(),
}),
computed: {
showFooter() {
@@ -304,13 +305,15 @@ export default {
return this.summary && this.$utils.htmlToText(this.summary) || '';
},
summaryElement() {
+ const purifiedHtml = this.summary && ExtendedDomPurify.purify(`${this.summary}
`) || '';
return {
- template: this.summary && ExtendedDomPurify.purify(`${this.summary}
`) || '',
+ template: this.emojiBankReady && purifiedHtml && EmojiAccessibility.addAccessibleNameToEmojis(purifiedHtml) || purifiedHtml,
};
},
titleElement() {
+ const purifiedHtml = this.title && ExtendedDomPurify.purify(`${this.title}`) || '';
return {
- template: this.title && ExtendedDomPurify.purify(`${this.title}`) || '',
+ template: this.emojiBankReady && purifiedHtml && EmojiAccessibility.addAccessibleNameToEmojis(purifiedHtml) || purifiedHtml,
};
},
bodyClass() {
@@ -370,6 +373,9 @@ export default {
created() {
this.retrieveActivityProperties();
window.addEventListener('resize', this.displayReadMore);
+ if (!this.emojiBankReady) {
+ EmojiAccessibility.ready.then(() => this.emojiBankReady = true);
+ }
},
mounted() {
this.displayReadMore();
diff --git a/webapp/src/main/webapp/vue-apps/application-toolbar/components/ApplicationToolbar.vue b/webapp/src/main/webapp/vue-apps/application-toolbar/components/ApplicationToolbar.vue
index 3346da32ffb..41d5e06c0d8 100644
--- a/webapp/src/main/webapp/vue-apps/application-toolbar/components/ApplicationToolbar.vue
+++ b/webapp/src/main/webapp/vue-apps/application-toolbar/components/ApplicationToolbar.vue
@@ -128,26 +128,22 @@
:max-width="rightTextFilter.maxWidth"
:class="expandFilter && 'flex-grow-1'"
flat>
-
-
-
-
- {{ rightTextFilter.tooltip }}
-
+