media-tag accessibility and UX improvements

* preview content and prompt users to describe media when uploading
* add 'alt' attributes to rendered media-tag content if it is included in the file's encrypted metadata
* add alt attributes to some related UI elements
pull/1/head
ansuz 3 years ago
parent e074b36761
commit 7231fc6926

@ -273,4 +273,13 @@
} }
} }
} }
#cp-upload-preview-container {
max-width: 100%;
media-tag {
max-width: 100%;
& > * {
max-width: 100%;
}
}
}
} }

@ -1658,6 +1658,7 @@ define([
UI.openCustomModal(modal); UI.openCustomModal(modal);
}; };
Messages.toolbar_userMenuAlt = "User menu"; // XXX
UIElements.createUserAdminMenu = function (Common, config) { UIElements.createUserAdminMenu = function (Common, config) {
var metadataMgr = Common.getMetadataMgr(); var metadataMgr = Common.getMetadataMgr();
@ -1990,8 +1991,11 @@ define([
*/ */
var $displayName = $userAdmin.find('.'+displayNameCls); var $displayName = $userAdmin.find('.'+displayNameCls);
$userAdmin.attr({ // XXX is this on the right element?
alt: Messages.toolbar_userMenuAlt,
});
var $avatar = $userAdmin.find('> button .cp-dropdown-button-title'); // XXX alt="User menu" var $avatar = $userAdmin.find('> button .cp-dropdown-button-title');
var loadingAvatar; var loadingAvatar;
var to; var to;
var oldUrl = ''; var oldUrl = '';

@ -73,7 +73,6 @@ var factory = function () {
* @param {object} cfg Object {Plugins, allowed, download, pdf} containing infos about plugins * @param {object} cfg Object {Plugins, allowed, download, pdf} containing infos about plugins
* @param {function} cb Callback function: (err, pluginElement) => {} * @param {function} cb Callback function: (err, pluginElement) => {}
*/ */
// XXX add alt attributes if present in metadata
text: function (metadata, url, content, cfg, cb) { text: function (metadata, url, content, cfg, cb) {
var plainText = document.createElement('div'); var plainText = document.createElement('div');
plainText.className = "plain-text-reader"; plainText.className = "plain-text-reader";
@ -88,6 +87,7 @@ var factory = function () {
image: function (metadata, url, content, cfg, cb) { image: function (metadata, url, content, cfg, cb) {
var img = document.createElement('img'); var img = document.createElement('img');
img.setAttribute('src', url); img.setAttribute('src', url);
img.setAttribute('alt', metadata.alt || "");
img.blob = content; img.blob = content;
cb(void 0, img); cb(void 0, img);
}, },
@ -95,15 +95,19 @@ var factory = function () {
var video = document.createElement('video'); var video = document.createElement('video');
video.setAttribute('src', url); video.setAttribute('src', url);
video.setAttribute('controls', true); video.setAttribute('controls', true);
// https://discuss.codecademy.com/t/can-we-use-an-alt-attribute-with-the-video-tag/300322/4
video.setAttribute('title', metadata.alt || "");
cb(void 0, video); cb(void 0, video);
}, },
audio: function (metadata, url, content, cfg, cb) { audio: function (metadata, url, content, cfg, cb) {
var audio = document.createElement('audio'); var audio = document.createElement('audio');
audio.setAttribute('src', url); audio.setAttribute('src', url);
audio.setAttribute('controls', true); audio.setAttribute('controls', true);
audio.setAttribute('alt', metadata.alt || "");
cb(void 0, audio); cb(void 0, audio);
}, },
pdf: function (metadata, url, content, cfg, cb) { pdf: function (metadata, url, content, cfg, cb) {
// XXX alt text
var iframe = document.createElement('iframe'); var iframe = document.createElement('iframe');
if (cfg.pdf.viewer) { // PDFJS if (cfg.pdf.viewer) { // PDFJS
var viewerUrl = cfg.pdf.viewer + '?file=' + url; var viewerUrl = cfg.pdf.viewer + '?file=' + url;
@ -116,6 +120,7 @@ var factory = function () {
download: function (metadata, url, content, cfg, cb) { download: function (metadata, url, content, cfg, cb) {
var btn = document.createElement('button'); var btn = document.createElement('button');
btn.setAttribute('class', 'btn btn-default'); btn.setAttribute('class', 'btn btn-default');
btn.setAttribute('alt', metadata.alt || "");
btn.innerHTML = '<i class="fa fa-save"></i>' + cfg.download.text + '<br>' + btn.innerHTML = '<i class="fa fa-save"></i>' + cfg.download.text + '<br>' +
(metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : ''); (metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '');
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
@ -543,7 +548,7 @@ var factory = function () {
// Process // Process
var process = function (mediaObject, decrypted, cfg, cb) { var process = function (mediaObject, decrypted, cfg, cb) {
var metadata = decrypted.metadata; var metadata = decrypted.metadata || {};
var blob = decrypted.content; var blob = decrypted.content;
var mediaType = getType(mediaObject, metadata, cfg); var mediaType = getType(mediaObject, metadata, cfg);
@ -597,6 +602,15 @@ var factory = function () {
}); });
}; };
var initHandlers = function () {
return {
'progress': [],
'complete': [],
'metadata': [],
'error': []
};
};
// Initialize a media-tag // Initialize a media-tag
var init = function (el, cfg) { var init = function (el, cfg) {
cfg = cfg || {}; cfg = cfg || {};
@ -614,13 +628,7 @@ var factory = function () {
}; };
} }
var handlers = cfg.handlers || { var handlers = cfg.handlers || initHandlers();
'progress': [],
'complete': [],
'metadata': [],
'error': []
};
var mediaObject = el._mediaObject = { var mediaObject = el._mediaObject = {
handlers: handlers, handlers: handlers,
tag: el tag: el
@ -763,6 +771,24 @@ var factory = function () {
init.fetchDecryptedMetadata = fetchDecryptedMetadata; init.fetchDecryptedMetadata = fetchDecryptedMetadata;
init.preview = function (content, metadata, cfg, cb) {
cfg = cfg || {};
addMissingConfig(cfg, config);
var handlers = cfg.handlers || initHandlers();
var el = document.createElement('media-tag');
var mediaObject = el._mediaObject = {
handlers: handlers,
tag: el,
};
process(mediaObject, {
metadata: metadata,
content: content
}, cfg, function (err) {
if (err) { return void cb(err); }
cb(void 0, el);
});
};
return init; return init;
}; };

@ -11,10 +11,12 @@ define([
'/common/hyperscript.js', '/common/hyperscript.js',
'/customize/messages.js', '/customize/messages.js',
'/customize/pages.js', '/customize/pages.js',
'/bower_components/nthen/index.js',
'/common/media-tag.js',
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, ApiConfig, FileCrypto, MakeBackup, Thumb, UI, UIElements, Util, Hash, h, Messages, Pages) { ], function ($, ApiConfig, FileCrypto, MakeBackup, Thumb, UI, UIElements, Util, Hash, h, Messages, Pages, nThen, MT) {
var Nacl = window.nacl; var Nacl = window.nacl;
var module = {}; var module = {};
@ -312,7 +314,11 @@ define([
}); });
return manualStore; return manualStore;
}; };
var fileUploadModal = function (defaultFileName, cb) {
Messages.upload_modal_alt = "Alt text"; // XXX
Messages.upload_addOptionalAlt = "Add descriptive text (optional)"; // XXX
var fileUploadModal = function (defaultFileName, cb, preview) {
var parsedName = /^(\.?.+?)(\.[^.]+)?$/.exec(defaultFileName) || []; var parsedName = /^(\.?.+?)(\.[^.]+)?$/.exec(defaultFileName) || [];
var ext = parsedName[2] || ""; var ext = parsedName[2] || "";
@ -321,9 +327,15 @@ define([
// Ask for name, password and owner // Ask for name, password and owner
var content = h('div', [ var content = h('div', [
h('h4', Messages.upload_modal_title), h('h4', Messages.upload_modal_title),
(preview? h('div#cp-upload-preview-container', preview): undefined),
UIElements.setHTML(h('label', {for: 'cp-upload-name'}), UIElements.setHTML(h('label', {for: 'cp-upload-name'}),
Messages._getKey('upload_modal_filename', [ext])), Messages._getKey('upload_modal_filename', [ext])),
h('input#cp-upload-name', {type: 'text', placeholder: defaultFileName, value: defaultFileName}), h('input#cp-upload-name', {type: 'text', placeholder: defaultFileName, value: defaultFileName}),
h('label', {for: 'cp-upload-alt'}, Messages.upload_addOptionalAlt), // XXX alt text for uploads
h('input#cp-upload-alt', {type: 'text', placeholder: Messages.upload_modal_alt}),
h('label', {for: 'cp-upload-password'}, Messages.addOptionalPassword), h('label', {for: 'cp-upload-password'}, Messages.addOptionalPassword),
UI.passwordInput({id: 'cp-upload-password'}), UI.passwordInput({id: 'cp-upload-password'}),
h('span', { h('span', {
@ -335,7 +347,8 @@ define([
manualStore manualStore
]); ]);
$(content).find('#cp-upload-owned').on('change', function () { var $content = $(content);
$content.find('#cp-upload-owned').on('change', function () {
var val = Util.isChecked($(content).find('#cp-upload-owned')); var val = Util.isChecked($(content).find('#cp-upload-owned'));
if (val) { if (val) {
$(content).find('#cp-upload-store').prop('checked', true).prop('disabled', true); $(content).find('#cp-upload-store').prop('checked', true).prop('disabled', true);
@ -348,8 +361,9 @@ define([
if (!yes) { return void cb(); } if (!yes) { return void cb(); }
// Get the values // Get the values
var newName = $(content).find('#cp-upload-name').val(); var newName = $content.find('#cp-upload-name').val();
var password = $(content).find('#cp-upload-password').val() || undefined; var password = $content.find('#cp-upload-password').val() || undefined;
var alt = $content.find('#cp-upload-alt').val() || undefined;
var owned = Util.isChecked($(content).find('#cp-upload-owned')); var owned = Util.isChecked($(content).find('#cp-upload-owned'));
var forceSave = owned || Util.isChecked($(content).find('#cp-upload-store')); var forceSave = owned || Util.isChecked($(content).find('#cp-upload-store'));
@ -366,7 +380,8 @@ define([
name: newName, name: newName,
password: password, password: password,
owned: owned, owned: owned,
forceSave: forceSave forceSave: forceSave,
alt: alt,
}); });
}); });
}; };
@ -437,6 +452,8 @@ define([
} }
var thumb; var thumb;
var preview;
var alt;
var file_arraybuffer; var file_arraybuffer;
var name = file.name; var name = file.name;
var password; var password;
@ -447,6 +464,7 @@ define([
var metadata = { var metadata = {
name: name, name: name,
type: type, type: type,
alt: alt,
}; };
if (thumb) { metadata.thumbnail = thumb; } if (thumb) { metadata.thumbnail = thumb; }
queue.push({ queue.push({
@ -486,8 +504,9 @@ define([
password = obj.password; password = obj.password;
owned = obj.owned; owned = obj.owned;
forceSave = obj.forceSave; forceSave = obj.forceSave;
alt = obj.alt;
finish(); finish();
}); }, preview);
} }
}; };
@ -495,11 +514,20 @@ define([
if (e) { console.error(e); } if (e) { console.error(e); }
file_arraybuffer = buffer; file_arraybuffer = buffer;
if (!Thumb.isSupportedType(file)) { return getName(); } if (!Thumb.isSupportedType(file)) { return getName(); }
// make a resized thumbnail from the image.. nThen(function (w) {
Thumb.fromBlob(file, function (e, thumb64) { // make a resized thumbnail from the image..
if (e) { console.error(e); } Thumb.fromBlob(file, w(function (e, thumb64) {
if (!thumb64) { return getName(); } if (e) { console.error(e); }
thumb = thumb64; if (!thumb64) { return; }
thumb = thumb64;
}));
MT.preview(buffer, {
type: file.type,
}, void 0, w(function (err, el) {
if (err) { return void console.error(err); }
preview = el;
}));
}).nThen(function () {
getName(); getName();
}); });
}); });

@ -341,6 +341,8 @@ define([
}); });
}; };
Messages.profile_defaultAlt = "Default profile picture"; // XXX
var displayAvatar = function (val) { var displayAvatar = function (val) {
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
var $span = APP.$avatar; var $span = APP.$avatar;
@ -349,7 +351,7 @@ define([
$('<img>', { $('<img>', {
src: '/customize/images/avatar.png', src: '/customize/images/avatar.png',
title: Messages.profile_avatar, title: Messages.profile_avatar,
alt: 'Avatar' // XXX translate this "Default profile picture" alt: Messages.profile_defaultAlt,
}).appendTo($span); }).appendTo($span);
return; return;
} }

Loading…
Cancel
Save